1 // Copyright (c) 2001, 2004, 2006, 2016 Per M.A. Bothner. 2 // This is free software; for terms and warranty disclaimer see ./COPYING. 3 4 package gnu.kawa.io; 5 import gnu.kawa.util.IntHashTable; 6 import java.io.*; 7 import gnu.mapping.ThreadLocation; 8 import gnu.lists.LList; 9 import gnu.lists.PrintConsumer; 10 import gnu.lists.Strings; 11 12 /** 13 * A pretty printer. 14 * 15 * <p> 16 * This code is transcribed from pprint.lisp in Steel Bank Common Lisp, 17 * which is again based on the code in CMU Common Lisp. 18 * Modifications have been made to accommodate shared structures, as described 19 * in SRFI-38. 20 * </p> 21 * <p> 22 * The pretty printer (hereafter PP) is responsible for formatting the results 23 * of evaluating expressions so that the human user can easily read them. The PP 24 * may also be responsible for choosing where to break lines so that the output 25 * looks reasonable. 26 * </p> 27 * <p> 28 * Raw text that has not yet been written out is stored in the "buffer" array, 29 * while information about the structure of the text is stored in a queue of 30 * "formatting tokens" (see below) called queueInts. A circular queue is used 31 * when the PP is not dealing with shared structures, and a linear array is used 32 * when it is. This only effects how the formatting tokens are enqueued into the 33 * structure, and how the structure is dynamically expanded. 34 * </p> 35 * <p> 36 * Formatting tokens are simply contiguous chunks of the queue. All tokens have 37 * a type and a size. For example, there is a formatting token called 38 * {@code QITEM_NEWLINE_TYPE} whose size is specified by {@code QITEM_NEWLINE_SIZE}. The size 39 * includes these type and size bits. The tokens use the space allocated to them 40 * in different ways. For example, the tab token uses two of its chunks ({@code int}s) 41 * to specify at which column it was entered, and to which column it should 42 * expand to. 43 * </p> 44 * <p> 45 * The tokens are buffered by a display formatter. The display formatter walks 46 * the structure to be printed emitting these tokens when it finds it has to, 47 * as well as the actual characters to be printed! Remember, the formatting 48 * tokens are distinct from the actual characters written, they just control how the 49 * characters are displayed. To accommodate this dichotomy, the character buffer 50 * holds the actual characters as they appear to the display formatter, the queue 51 * holds the formatting tokens plus a mapping that specifies which character in 52 * the buffer they end at. 53 * </p> 54 * <p> 55 * Once the display formatter has finished walking the expression to be printed, 56 * the formatting queue is walked by the "destructive" methods, i.e., the ones 57 * that actually flush the buffer to the output port. As mentioned above, each 58 * token encodes the position it references in the character buffer, so we use 59 * this information to work our way through the buffer, occasionally modifying 60 * the buffer to look pretty (dictated by the formatting tokens). Examples of 61 * such modifications include changing a space character to a newline character 62 * so that the line will prettily fit on the specified line width, or more 63 * drastically, remove a shared object of the expression and replacing 64 * it will print-circle notation. 65 * </p> 66 * <p> 67 * Once everything has been flushed, the initial conditions are reset ready for 68 * the next printing 69 * </p> 70 * 71 * @author Per Bothner 72 * @author Charles Turner 73 */ 74 75 public class PrettyWriter extends PrintConsumer 76 { 77 /** 78 * Construct a PrettyWriter with {@code prettyPrintingMode = 1} 79 * @param out The output to write to 80 * @see #prettyPrintingMode 81 */ PrettyWriter(java.io.Writer out)82 public PrettyWriter(java.io.Writer out) 83 { 84 super(out); 85 setPrettyPrintingMode(1); 86 } 87 /** 88 * Construct a PrettyWriter which breaks on a given line length. 89 * If {@code lineLength} is strictly greater than one, 90 * {@code prettyPrintingMode} is set to 1, otherwise it's set to 0. 91 * 92 * @param out The output to write to 93 * @param lineLength The column width lines should break on 94 * @see #prettyPrintingMode #lineLength 95 */ PrettyWriter(java.io.Writer out, int lineLength)96 public PrettyWriter(java.io.Writer out, int lineLength) 97 { 98 super(out); 99 this.lineLength = lineLength; 100 setPrettyPrintingMode(lineLength > 1 ? 1 : 0); 101 } 102 103 /** 104 * Construct a PrettyWriter. 105 * @param out The output port to write to 106 * @param prettyPrintingMode If {@code true} then {@code prettyPrintingMode = 1} 107 * otherwise {@code prettyPrintingMode = 0} 108 */ PrettyWriter(java.io.Writer out, boolean prettyPrintingMode)109 public PrettyWriter(java.io.Writer out, boolean prettyPrintingMode) 110 { 111 super(out); 112 setPrettyPrintingMode(prettyPrintingMode ? 1 : 0); 113 } 114 115 /** Line length we should format to. */ 116 int lineLength = 80; 117 118 /** 119 * If the right-hand-side margin is less than or equal to this value, the 120 * miser printing mode is entered. This is compact style of printing where 121 * line breaks are taken at every inter-token space. 122 * (1 123 * 2 124 * 3) is an example of a miser-style printing. 125 */ 126 int miserWidth = 40; 127 128 boolean isDomTerm; 129 @Override isDomTerm()130 public boolean isDomTerm() { return isDomTerm; } 131 132 /** 133 * This variable is used to determine how the queueInts array should be 134 * expanded. When printing with shared structure, we don't want a circular 135 * queue, since we rely on being able to reference formatting tokens in 136 * previous queue slots. When the queue has wrapped and expanded several times, 137 * keeping track of such references is a nightmare. 138 */ 139 boolean sharing = false; 140 141 /** 142 * When the display formatter has written a back-reference, we know for certain 143 * that print-circle notation has been emitted. 144 * This variable is used to avoid the expensive resolveBackReferences method 145 * from running in the case that we have a non-recursive object. 146 */ 147 boolean reallySharing = false; 148 149 public static ThreadLocation lineLengthLoc 150 = new ThreadLocation("line-length"); 151 public static ThreadLocation miserWidthLoc 152 = new ThreadLocation("miser-width"); 153 public static ThreadLocation indentLoc 154 = new ThreadLocation("indent"); 155 156 /** 157 * Whether to resolve shared structures is dependent on the out:print-circle 158 * command line switch being set true. The PrettyWriter uses a different 159 * data structure to resolve back-references when we're emitting print-circle 160 * notation. 161 */ 162 public static ThreadLocation isSharing 163 = new ThreadLocation("print-circle"); 164 165 /** The current pretty-printing mode. 166 * See setPrettyPrintingMode for valid values. */ 167 private int prettyPrintingMode; 168 169 private IntHashTable idhash; 170 initialiseIDHash()171 public boolean initialiseIDHash() { 172 Object share = isSharing.get(null); // FIXME ??? 173 if (idhash != null) 174 return false; 175 idhash = new IntHashTable(); 176 return true; 177 } 178 clearIDHash()179 public void clearIDHash() 180 { 181 idhash.clear(); 182 } 183 finishIDHash()184 public void finishIDHash() { 185 idhash.clear(); 186 writeEndOfExpression(); 187 resolveBackReferences(); 188 flush(); 189 idhash = null; 190 } 191 IDHashLookup(Object obj)192 public int IDHashLookup(Object obj) { 193 return idhash.lookup(obj); 194 } 195 IDHashGetFromIndex(int index)196 public int IDHashGetFromIndex(int index) { 197 return idhash.getFromIndex(index); 198 } 199 IDHashPutAtIndex(Object obj, int value, int index)200 public int IDHashPutAtIndex(Object obj, int value, int index) { 201 return idhash.putAtIndex(obj, value, index); 202 } 203 IDHashRemove(Object obj)204 public int IDHashRemove(Object obj) { 205 return idhash.remove(obj); 206 } 207 208 /** Control pretty-printing mode. 209 * @param mode the value 0 disables pretty-printing; 210 * the value 1 enables explicit pretty-printing; 211 * the value 2 enables pretty-printing with auto-fill, which means that 212 * spaces are treated like enqueing NEWLINE_SPACE (essentiall a 'fill'). 213 */ setPrettyPrintingMode(int mode)214 public void setPrettyPrintingMode (int mode) { 215 prettyPrintingMode = mode; 216 } 217 setSharing(boolean sharing)218 public void setSharing (boolean sharing) { 219 this.sharing = sharing; 220 } 221 222 /** Return pretty-printing mode. 223 * @return 0, 1, 2, as described for {@link #setPrettyPrintingMode(int)}. 224 */ getPrettyPrintingMode()225 public int getPrettyPrintingMode () { return prettyPrintingMode; } 226 227 /** Is pretty printing enabled? */ isPrettyPrinting()228 public boolean isPrettyPrinting () { return prettyPrintingMode > 0; } 229 230 /** Turn pretty printing on or off. 231 * Equivalent to {@code setPrettyPrintingMode(mode?1:0)}. 232 */ setPrettyPrinting(boolean mode)233 public void setPrettyPrinting (boolean mode) 234 { 235 setPrettyPrintingMode(mode ? 1 : 0); 236 } 237 238 /** Should we write directly to out without using buffer? 239 * Currently, this is always false, but if you want to support 240 * "unbuffered mode", you need to change this. 241 */ isPassingThrough()242 private boolean isPassingThrough() { 243 return buffer.length == 0 && out != null; 244 } 245 246 /** Holds all the text that has been output but not yet printed. */ 247 private char[] buffer = new char[2048]; getBuffer()248 char[] getBuffer() { return buffer; } 249 250 /** The index into BUFFER where more text should be put. */ 251 private int bufferFillPointer; getFillIndex()252 int getFillIndex() { return bufferFillPointer; } setFillIndex(int index)253 void setFillIndex(int index) { bufferFillPointer = index; } 254 255 /** Total amount of stuff that has been shifted out of the buffer. 256 * Whenever we output stuff from the buffer, we shift the remaining noise 257 * over. This makes it difficult to keep references to locations in 258 * the buffer. */ 259 int bufferOffset; 260 261 /** The column the first character in the buffer will appear in. 262 * Normally zero, but if we end up with a very long line with no breaks in it 263 * we might have to output part of it. Then this will no longer be zero. 264 * Ditto after emitting a prompt. */ 265 int bufferStartColumn; 266 267 /** The line number we are currently on. Used for *print-lines* abrevs and 268 * to tell when sections have been split across multiple lines. */ 269 int lineNumber; 270 271 // There are three different units for measuring character positions: 272 // COLUMN - offset (in characters) from the start of the current line. 273 // INDEX - index into the output buffer. 274 // POSN - some position in the stream of characters cycling through 275 // the output buffer. 276 277 /** 278 * Return the adjusted index into the {@link #buffer}. The position is adjusted 279 * such this index will point to data that has not already been printed. 280 * @param index The index into the {@link #buffer} 281 * @return The adjusted index accounting for already written data 282 */ indexPosn(int index)283 private int indexPosn (int index) 284 { 285 return index + bufferOffset; 286 } 287 288 /** 289 * Return the "real" index to {@link #buffer} which may index data already 290 * written. Useful for QITEM_POSN's 291 * @param posn The adjusted index into the {@link #buffer} 292 * @return The "real" index into the {@link #buffer} 293 * @see #indexPosn(int) 294 */ posnIndex(int posn)295 private int posnIndex (int posn) 296 { 297 return posn - bufferOffset; 298 } 299 300 /** 301 * Returns the index of a QITEM position relative to the output buffer 302 * @param posn The QITEM position to be processed 303 * @return The position of this QITEM relative to the {@link #buffer} 304 */ posnColumn(int posn)305 private int posnColumn (int posn) 306 { 307 return indexColumn(posnIndex(posn)); 308 } 309 310 /** Stack of logical blocks in effect at the buffer start. 311 * I.e. blocks for which {@code reallyStartLogicalBlock} has been called. 312 * Each block uses {@code LOGICAL_BLOCK_LENGTH} {@code int} in this array. */ 313 int[] blocks = new int[10 * LOGICAL_BLOCK_LENGTH]; 314 /** Number of {@code int}s used by each block in the {@code blocks} array. */ 315 static final private int LOGICAL_BLOCK_LENGTH = 6; 316 static final private int BLOCK_START_COLUMN = -1; 317 static final private int BLOCK_SECTION_COLUMN = -2; 318 static final private int BLOCK_PER_LINE_PREFIX_END = -3; 319 static final private int BLOCK_PREFIX_LENGTH = -4; 320 static final private int BLOCK_SUFFIX_LENGTH = -5; 321 static final private int BLOCK_SECTION_START_LINE = -6; 322 /** The "stack pointer" in the {@code blocks} array. */ 323 int blockDepth = LOGICAL_BLOCK_LENGTH; 324 325 /** Buffer holding the per-line prefix active at the buffer start. 326 * Indentation is included in this. The length of this is stored 327 * in the logical block stack. */ 328 char[] prefix = new char[128]; 329 330 /** Buffer holding the total remaining suffix active at the buffer start. 331 * The characters are right-justified in the buffer to make it easier 332 * to output the buffer. The length is stored in the logical block stack. */ 333 char[] suffix = new char[128]; 334 335 static final int QUEUE_INIT_ALLOC_SIZE = 300; // FIXME 336 337 /** A queue of pending operations. 338 * This is primarily stored in the circular buffer queueInts. There 339 * are different kinds of operation types, and each operation can 340 * require a variable number of elements in the buffer, depending on 341 * the operation type. Given an operation at 'index', the type 342 * operation type code is 'getQueueType(index)' (one of the 343 * QITEM_XXX_TYPE macros below), and the number of elements in the 344 * buffer is 'getQueueSize(index)' (one of the QITEM_XXX_SIZE values 345 * below). You can think of the various QITEM_XXX_TYPEs as 346 * "sub-classes" of queued operations, but instead of creating 347 * actual Java objects, we allocate the objects' fields in the 348 * queueInts and QueueStrings arrays, to avoid expensive object 349 * allocation. The special QITEM_NOP_TYPE is a used as a marker for 350 * when there isn't enough space in the rest of buffer, so we have 351 * to wrap around to the start. The other QITEM_XXX macros are the 352 * offsets of the various "fields" relative to the start index. */ 353 int[] queueInts = new int[QUEUE_INIT_ALLOC_SIZE]; 354 355 /** For simplicity, queueStrings is the same size as queueInts. */ 356 String[] queueStrings = new String[QUEUE_INIT_ALLOC_SIZE]; 357 /** Index in queueInts and queueStrings of oldest enqueued operation. */ 358 int queueTail; 359 /** Number of elements (in queueInts and queueStrings) in use. */ 360 private int queueSize; 361 /** If >= 0, index (into queueInts) of current unclosed begin-block node. 362 * This is a head of a linked linked of queued BLOCK_START for which 363 * we haven't seen the matching BLOCK_END */ 364 int currentBlock = -1; 365 /** Number of startLogicalBlock - number of endLogicalBlock. */ 366 public int pendingBlocksCount; 367 368 /** 369 * The queue types and respective sizes are packed into a 32-bit integer. 370 * 371 * The most-significant 16-bits of a QITEM defines the type code. This type 372 * code is defined by one of the QITEM_.+_TYPE fields below. 373 * The least significant 8-bits of QITEM defines the type size. This size 374 * is defined by one of the QITEM_.+_SIZE fields below. 375 * Bits (8, 16] are currently used to store flags for their respective queue 376 * types 377 * The only exception to this rule is QITEM_NOP_TYPE, which acts as a marker 378 * for when there isn't enough space in the rest of the buffer. 379 * All other QITEMs have in the second word (at offset QITEM_POSN) 380 * the corresponding text position as a POSN. 381 */ 382 383 /** The first int of a QITEM contains its type code and size. */ 384 static final int QITEM_TYPE_AND_SIZE = 0; getQueueType(int index)385 private int getQueueType(int index) { return queueInts[index] & 0xFF; } getQueueSize(int index)386 private int getQueueSize(int index) { return queueInts[index] >> 16; } 387 /** Relative offset of POSN field of a QITEM. */ 388 static final int QITEM_POSN = 1; 389 /** Size of "base part" of a QITEM. The type and size packing, plus the position */ 390 static final int QITEM_BASE_SIZE = 2; 391 392 /** A dummy queue item used at the high end of the queue buffer 393 * when there isn't enough space for the needed queue item. */ 394 static final int QITEM_NOP_TYPE = 0; 395 396 /** "Abstract" type for beginning of section. 397 * A section is from a block-start to a newline, from a newline to 398 * the next newline (in the same block?), or from a newline to 399 * the block end (?). */ 400 /*static final int QITEM_SECTION_START_TYPE = 1;*/ 401 static final int QITEM_SECTION_START_SIZE = QITEM_BASE_SIZE + 2; 402 static final int QITEM_SECTION_START_DEPTH = QITEM_BASE_SIZE; 403 static final int QITEM_SECTION_START_SECTION_END = QITEM_BASE_SIZE + 1; 404 405 /** A newline queue item. */ 406 static final int QITEM_NEWLINE_TYPE = 2; 407 static final int QITEM_NEWLINE_SIZE = QITEM_SECTION_START_SIZE + 1; 408 static final int QITEM_NEWLINE_KIND = QITEM_SECTION_START_SIZE; 409 public static final int NEWLINE_LINEAR = 'N'; 410 public static final int NEWLINE_LITERAL = 'L'; 411 public static final int NEWLINE_FILL = 'F'; 412 /** A non-nested ' ' gets an implicit NEWLINE_SPACE. 413 * This is treated similarly to NEWLINE_FILL, but not quite. */ 414 public static final int NEWLINE_SPACE = 'S'; 415 public static final int NEWLINE_MISER = 'M'; 416 public static final int NEWLINE_MANDATORY = 'R'; // "required" 417 /** Treat like NEWLINE_LITERAL in terms of ending sections, 418 * but don't actually print anything. */ 419 public static final int NEWLINE_DUMMY = 'D'; 420 421 static final int QITEM_INDENTATION_TYPE = 3; 422 static final int QITEM_INDENTATION_SIZE = QITEM_BASE_SIZE + 2; 423 static final int QITEM_INDENTATION_KIND = QITEM_BASE_SIZE; 424 static final char QITEM_INDENTATION_BLOCK = 'B'; 425 static final char QITEM_INDENTATION_CURRENT = 'C'; 426 static final int QITEM_INDENTATION_AMOUNT = QITEM_BASE_SIZE + 1; 427 428 /** A "block-start" queue item. */ 429 static final int QITEM_BLOCK_START_TYPE = 4; 430 static final int QITEM_BLOCK_START_SIZE = QITEM_SECTION_START_SIZE + 3; 431 /** If the QITEM_BLOCK_START_BLOCK_END < 0, it points to 432 * the previous (outer) un-closed block-start. 433 * If QITEM_BLOCK_START_BLOCK_END > 0, it points to the 434 * corresponding block-end node. 435 * In both cases the pointers are relative to the current BLOCK_START. */ 436 static final int QITEM_BLOCK_START_BLOCK_END = QITEM_SECTION_START_SIZE; 437 static final int QITEM_BLOCK_START_PREFIX = QITEM_SECTION_START_SIZE + 1; 438 static final int QITEM_BLOCK_START_SUFFIX = QITEM_SECTION_START_SIZE + 2; 439 440 static final int QITEM_BLOCK_END_TYPE = 5; 441 static final int QITEM_BLOCK_END_SIZE = QITEM_BASE_SIZE; 442 443 static final int QITEM_TAB_TYPE = 6; 444 static final int QITEM_TAB_SIZE = QITEM_BASE_SIZE + 3; 445 static final int QITEM_TAB_FLAGS = QITEM_BASE_SIZE; 446 static final int QITEM_TAB_IS_SECTION = 1; 447 static final int QITEM_TAB_IS_RELATIVE = 2; 448 static final int QITEM_TAB_COLNUM = QITEM_BASE_SIZE + 1; 449 static final int QITEM_TAB_COLINC = QITEM_BASE_SIZE + 2; 450 451 /** 452 * Position markers are used to "name" shared parts of an expression. 453 * For instance in #1=(a #1# c), #1= is the position marker. 454 * The GROUP_TYPE is used as a bit mask to determine from the type+size 455 * packing whether this position marker is grouping a sub-object. See the 456 * PAIR_END_TYPE comments below for a more detailed description of this. 457 */ 458 static final int QITEM_POSNMARKER_TYPE = 7; 459 static final int QITEM_POSNMARKER_GROUP_TYPE = 8; 460 static final int QITEM_POSNMARKER_SIZE = QITEM_BASE_SIZE + 1; 461 /** The datum label to use for this object, or 0 if no label. 462 * The label is initially 0, indicating no back-references. 463 * It is set to 1 when a back-reference is seen. 464 * Finally resolveBackReferences sets it to a ordinal that gets written 465 * out - i.e. the N in {@code #N#}. 466 */ 467 static final int QITEM_POSNMARKER_LOC = QITEM_BASE_SIZE; 468 469 /** 470 * Represents a back reference to a value defined by a previous QITEM_POSNMARKER 471 * @param posn The index into the {@link #queueInts} array of the position 472 * marker 473 * @return true if the position marker has been referenced. 474 */ 475 static final int QITEM_BACKREF_TYPE = 8; 476 static final int QITEM_BACKREF_SIZE = QITEM_BASE_SIZE + 1; 477 static final int QITEM_BACKREF_TARGET = QITEM_BASE_SIZE; 478 479 /** 480 * Pair end types are also linked to position markers. If the position 481 * marker is found to be grouping (i.e. that it references a non-initial 482 * sublist) the printer must emit an extra closing parenthesis so that the 483 * position marker correctly points to referenced sublist. 484 * 485 * '(a *b c d*).. 486 * 487 * For instance, if the '(b c d) sublist was found to be a reference, we would 488 * need to add extra parenthesis around the sublist like so, 489 * 490 * '(a . #N=(b c d))... 491 * 492 * These types deal with the closing parenthesis of such cases. 493 */ 494 static final int QITEM_PAIR_END_TYPE = 9; 495 static final int QITEM_PAIR_END_SIZE = QITEM_BASE_SIZE + 1; 496 static final int QITEM_PAIR_END_REF = QITEM_BASE_SIZE; 497 498 /** 499 * When performing a shared structure printing, with a prettyPrintingMode of 500 * zero, the only queueItem tokens contained in the string are newline tokens. 501 * This isn't enough for the back-reference resolver. This token is appended 502 * to the end of an s-exp so that the resolver will be able consume all of the 503 * expression. 504 */ 505 static final int QITEM_EOE_TYPE = 10; 506 static final int QITEM_EOE_SIZE = QITEM_BASE_SIZE; 507 508 /** 509 * To determine whether the position maker should group what it references. 510 * This is necessary when the CDR of a non-initial pair has been found to be 511 * referenced. We explout some unused space in the queue item 512 * type+size packing to accomodate this information. 513 * @param posn The position marker under test 514 * @return true if this position marker should group 515 */ posnMarkerIsGrouping(int posn)516 private boolean posnMarkerIsGrouping (int posn) 517 { 518 return ((queueInts[posn] >> QITEM_POSNMARKER_GROUP_TYPE) & 1) != 0; 519 } 520 521 /** 522 * To determine whether a position marker has been referenced 523 * @param posn The index into the {@link #queueInts} array of the position 524 * marker 525 * @return true if the position marker has been referenced. 526 */ posnMarkerActive(int posn)527 private boolean posnMarkerActive (int posn) 528 { 529 return queueInts[posn + QITEM_POSNMARKER_LOC] != 0; 530 } 531 getSectionColumn()532 private int getSectionColumn() 533 { 534 return blocks[blockDepth+BLOCK_SECTION_COLUMN]; 535 } 536 getStartColumn()537 private int getStartColumn() 538 { 539 return blocks[blockDepth+BLOCK_START_COLUMN]; 540 } 541 getPerLinePrefixEnd()542 private int getPerLinePrefixEnd() 543 { 544 return blocks[blockDepth+BLOCK_PER_LINE_PREFIX_END]; 545 } 546 getPrefixLength()547 private int getPrefixLength() 548 { 549 return blocks[blockDepth+BLOCK_PREFIX_LENGTH]; 550 } 551 getSuffixLength()552 private int getSuffixLength() 553 { 554 return blocks[blockDepth+BLOCK_SUFFIX_LENGTH]; 555 } 556 getSectionStartLine()557 private int getSectionStartLine() 558 { 559 return blocks[blockDepth+BLOCK_SECTION_START_LINE]; 560 } 561 562 boolean wordEndSeen; 563 564 /** Note the end of a "word". See {@link #writeWordStart}. */ writeWordEnd()565 public void writeWordEnd () 566 { 567 wordEndSeen = true; 568 } 569 570 /** Maybe write a word-separating space. 571 * Specifically, write a space if the previous output 572 * was {@link #writeWordEnd}. Otherwise, do nothing. 573 */ writeWordStart()574 public void writeWordStart () 575 { 576 if (wordEndSeen) 577 write(' '); 578 wordEndSeen = false; 579 } 580 581 @Override clearWordEnd()582 protected void clearWordEnd () { 583 wordEndSeen = false; 584 } 585 586 /** 587 * Write a character to the buffer. If we're pretty printing and the character 588 * is a space character, two cases arise. If the character is a newline, then 589 * enqueue a NEWLINE_LITERAL, which has to used. If the character is a space, 590 * then we may at our discretion place a newline there if the printing 591 * requires that we do so. Note that this doesn't mean we *will* print 592 * a newline, just that we can. For other characters, we just populate the 593 * buffer. 594 * @param ch The character to write. 595 */ write(int ch)596 public void write (int ch) 597 { 598 wordEndSeen = false; 599 //log("{WRITE-ch: "+((char)ch)+"}"); 600 if (isPassingThrough()) { 601 if (ch == '\n' || ch == '\r') 602 bufferStartColumn = 0; 603 writeToBase(ch); 604 } 605 else if (ch == '\n' && prettyPrintingMode > 0) 606 enqueueNewline(NEWLINE_LITERAL); 607 else 608 { 609 ensureSpaceInBuffer(1); 610 int fillPointer = bufferFillPointer; 611 buffer[fillPointer] = (char) ch; 612 bufferFillPointer = 1 + fillPointer; 613 if (ch == ' ' && prettyPrintingMode > 1 && currentBlock < 0) 614 enqueueNewline(NEWLINE_SPACE); 615 } 616 } 617 write(String str)618 public void write (String str) 619 { 620 write(str, 0, str.length()); 621 } 622 623 /** 624 * Write a (sub)string to the output buffer. Newlines and spaces are handled, 625 * and appropriate formatting token emitted in such cases. 626 * 627 * @param str The string to use 628 * @param start Where to start in the string 629 * @param count The number of character to write from the string 630 */ write(String str, int start, int count)631 public void write (String str, int start, int count) 632 { 633 wordEndSeen = false; 634 //log("{WRITE-str: "+str.substring(start, start+count)+"}"); 635 if (isPassingThrough()) { 636 for (int i = count; ; ) { 637 if (--i < 0) { 638 bufferStartColumn += count; 639 break; 640 } 641 char ch = str.charAt(start+i); 642 if (ch == '\r' || ch == '\n') { 643 bufferStartColumn = count - (i + 1); 644 break; 645 } 646 } 647 writeToBase(str, start, count); 648 return; 649 } 650 while (count > 0) 651 { 652 int cnt = count; 653 // May allocate for space than we need (if the buffer gets fluhed). FIXME 654 int available = ensureSpaceInBuffer(count); 655 if (cnt > available) 656 cnt = available; 657 int fillPointer = bufferFillPointer; 658 count -= cnt; 659 while (--cnt >= 0) 660 { 661 char ch = str.charAt(start++); 662 if (ch == '\n' && prettyPrintingMode > 0) 663 { 664 bufferFillPointer = fillPointer; 665 enqueueNewline(NEWLINE_LITERAL); 666 fillPointer = bufferFillPointer; 667 } 668 else 669 { 670 buffer[fillPointer++] = (char) ch; 671 if (ch == ' ' && prettyPrintingMode > 1 && currentBlock < 0) 672 { 673 bufferFillPointer = fillPointer; 674 enqueueNewline(NEWLINE_SPACE); 675 fillPointer = bufferFillPointer; 676 } 677 } 678 } 679 bufferFillPointer = fillPointer; 680 } 681 } 682 write(char[] str)683 public void write (char[] str) 684 { 685 write(str, 0, str.length); 686 } 687 write(char[] str, int start, int count)688 public void write (char[] str, int start, int count) 689 { 690 wordEndSeen = false; 691 //log("{WRITE: "+new String(str, start, count)+"}"); 692 if (isPassingThrough()) { 693 for (int i = count; ; ) { 694 if (--i < 0) { 695 bufferStartColumn += count; 696 break; 697 } 698 char ch = str[start+i]; 699 if (ch == '\r' || ch == '\n') { 700 bufferStartColumn = count - (i + 1); 701 break; 702 } 703 } 704 writeToBase(str, start, count); 705 return; 706 } 707 int end = start + count; 708 retry: 709 while (count > 0) 710 { 711 // Look for newline. Should be merged with following loop. FIXME. 712 for (int i = start; i < end; i++) 713 { 714 char c; 715 if (prettyPrintingMode > 0 716 && ((c = str[i]) == '\n' 717 || (c == ' ' && currentBlock < 0))) 718 { 719 write(str, start, i - start); // Recurse 720 write(c); 721 start = i + 1; 722 count = end - start; 723 continue retry; 724 } 725 } 726 727 for (;;) 728 { 729 int available = ensureSpaceInBuffer(count); 730 int cnt = available < count ? available : count; 731 int fillPointer = bufferFillPointer; 732 int newFillPtr = fillPointer + cnt; 733 for (int i = fillPointer; i < newFillPtr; i++) 734 buffer[i] = str[start++]; 735 bufferFillPointer = newFillPtr; 736 count -= cnt; 737 if (count == 0) 738 break; 739 } 740 } 741 } 742 743 private void writeToBase(char[] buf, int start, int count) { 744 try { 745 //log("writeToBase: "+ gnu.lists.Strings.toJson(new String(buf,start,count))); 746 out.write(buf, start, count); 747 } catch (IOException ex) { 748 throw new RuntimeException(ex); 749 } 750 } 751 752 private void writeToBase(String str, int start, int count) { 753 try { 754 //log("writeToBase: "+ gnu.lists.Strings.toJson(str.substring(start,start+count))); 755 out.write(str, start, count); 756 } catch (IOException ex) { 757 throw new RuntimeException(ex); 758 } 759 } 760 761 private void writeToBase(String str) { 762 writeToBase(str, 0, str.length()); 763 } 764 765 private void writeToBase(int ch) { 766 try { 767 //log("writeToBase: "+ gnu.lists.Strings.toJson(String.valueOf((char) ch))+"="+ch); 768 out.write(ch); 769 } catch (IOException ex) { 770 throw new RuntimeException(ex); 771 } 772 } 773 774 /** 775 * A position marker is queued for every Scheme object that *could* refer 776 * to itself (or other parts of the structure). 777 * Currently, this applies only to vectors and lists. For each 778 * such object, the printer records it. All position markers are assumed 779 * inactive (i.e. not back-referenced) until proven otherwise. Some position 780 * markers are required to group new sublists, the grouping variable 781 * notes this. 782 * @return Integer The index in {@link #queueInts} where this marker resides 783 * it's used for back-reference lookup. 784 * @see #writeBackReference(int) 785 */ 786 public int writePositionMarker (boolean grouping) 787 { 788 int result = enqueue(QITEM_POSNMARKER_TYPE, QITEM_POSNMARKER_SIZE); 789 // A zero implies we haven't assigned this position marker a count yet 790 queueInts[result + QITEM_POSNMARKER_LOC] = 0; 791 // Are we marking a non-initial sublist? 792 queueInts[result] |= (grouping ? 1 : 0) << QITEM_POSNMARKER_GROUP_TYPE; 793 // Return the index of this position marker in the queueInts 794 //log("POSNMARK: @"+result); 795 return result; 796 } 797 798 /** 799 * Enqueue a back-reference token to the formatting queue. 800 * 801 * When the display formatter has detected that an object is referring to an 802 * "enclosing" object, a back-reference is queued. This makes the printer 803 * emit suitable notation to demonstrate the shared structure. The position 804 * marker this back-reference references is made active. If the structure 805 * to be printed was really sharing before, it is after this method. 806 * @param posn The index in the {@link #queueInts} array of the referenced 807 * position marker. 808 */ 809 public void writeBackReference (int posn) 810 { 811 if (!reallySharing) 812 reallySharing = true; 813 int result = enqueue(QITEM_BACKREF_TYPE, QITEM_BACKREF_SIZE); 814 //log("BACKREF: @"+result+" references "+posn); 815 queueInts[result + QITEM_BACKREF_TARGET] = posn; 816 // The position marker this references is now active. 817 queueInts[posn + QITEM_POSNMARKER_LOC] = 1; 818 } 819 820 /** 821 * Enqueue a pair-end token to the formatting queue. 822 * 823 * When a position marker is grouping it creates a new sublist, which of 824 * course needs a closing parenthesis that's not originally present in the 825 * structure to be printed. This token informs the back reference resolver 826 * to emit an extra closing parenthesis so that the reader will be happy. 827 * 828 * @param posn The index in the {@link #queueInts} array of the referenced 829 * position marker. 830 */ 831 public void writePairEnd (Integer posn) 832 { 833 int result = enqueue(QITEM_PAIR_END_TYPE, QITEM_PAIR_END_SIZE); 834 //log("PAIREND: @"+result+" referencing: "+posn+" queueSize="+queueSize); 835 queueInts[result + QITEM_PAIR_END_REF] = posn; 836 } 837 838 public void writeEndOfExpression () 839 { 840 enqueue(QITEM_EOE_TYPE, QITEM_EOE_SIZE); 841 } 842 843 private void pushLogicalBlock(int column, 844 int perLineEnd, 845 int prefixLength, int suffixLength, 846 int sectionStartLine) 847 { 848 int newLength = blockDepth + LOGICAL_BLOCK_LENGTH; 849 if (newLength >= blocks.length) 850 { 851 int[] newBlocks = new int[2 * blocks.length]; 852 System.arraycopy(blocks, 0, newBlocks, 0, blockDepth); 853 blocks = newBlocks; 854 } 855 blockDepth = newLength; 856 blocks[blockDepth + BLOCK_START_COLUMN] = column; 857 blocks[blockDepth + BLOCK_SECTION_COLUMN] = column; 858 blocks[blockDepth + BLOCK_PER_LINE_PREFIX_END] = perLineEnd; 859 blocks[blockDepth + BLOCK_PREFIX_LENGTH] = prefixLength; 860 blocks[blockDepth + BLOCK_SUFFIX_LENGTH] = suffixLength; 861 blocks[blockDepth + BLOCK_SECTION_START_LINE] = sectionStartLine; 862 } 863 864 /** 865 * 866 * @param column the column of the output port where this block starts 867 * @param prefix the block's prefix 868 * @param suffix the block's suffix 869 */ 870 void reallyStartLogicalBlock(int column, String prefix, String suffix) 871 { 872 int perLineEnd = getPerLinePrefixEnd(); 873 int prefixLength = getPrefixLength(); 874 int suffixLength = getSuffixLength(); 875 pushLogicalBlock(column, perLineEnd, prefixLength, suffixLength, 876 lineNumber); 877 if (!isDomTerm()) 878 setIndentation(column); 879 if (prefix != null) 880 { 881 blocks[blockDepth + BLOCK_PER_LINE_PREFIX_END] = column; 882 int plen = prefix.length(); 883 prefix.getChars(0, plen, this.prefix, column - plen); 884 } 885 if (suffix != null) 886 { 887 // Prepend the new suffix in front of the old suffix in this.suffix. 888 // The suffix is stored at the "right" (high-index) end of 889 // this.suffix to make it easier to prepend new suffixes. 890 char[] totalSuffix = this.suffix; 891 int totalSuffixLen = totalSuffix.length; 892 int additional = suffix.length(); 893 int newSuffixLen = suffixLength + additional; 894 if (newSuffixLen > totalSuffixLen) 895 { 896 int newTotalSuffixLen = enoughSpace(totalSuffixLen, additional); 897 this.suffix = new char[newTotalSuffixLen]; 898 System.arraycopy(totalSuffix, totalSuffixLen - suffixLength, 899 this.suffix, newTotalSuffixLen - suffixLength, 900 suffixLength); 901 totalSuffixLen = newTotalSuffixLen; 902 } 903 suffix.getChars(0, additional, 904 totalSuffix, totalSuffixLen - newSuffixLen); 905 blocks[blockDepth + BLOCK_SUFFIX_LENGTH] = newSuffixLen; 906 } 907 908 } 909 910 int enqueueTab (int flags, int colnum, int colinc) // DONE 911 { 912 int addr = enqueue(QITEM_TAB_TYPE, QITEM_TAB_SIZE); 913 queueInts[addr + QITEM_TAB_FLAGS] = flags; 914 queueInts[addr + QITEM_TAB_COLNUM] = colnum; 915 queueInts[addr + QITEM_TAB_COLINC] = colinc; 916 return addr; 917 } 918 919 /** Calculate how much space to allocate for a buffer. 920 * @param current the current size of the buffer 921 * @param want how much more space is needed 922 */ 923 private static int enoughSpace(int current, int want) 924 { 925 int scaled = current + (current>>1); // Multiply by 1.5 926 int enough = current + ((5 * want) >> 2); 927 return scaled > enough ? scaled : enough; 928 } 929 setIndentation(int column)930 private void setIndentation(int column) 931 { 932 char[] prefix = this.prefix; 933 int prefixLen = prefix.length; 934 int current = getPrefixLength(); 935 int minimum = getPerLinePrefixEnd(); 936 if (minimum > column) 937 column = minimum; 938 if (column > prefixLen) 939 { 940 prefix = new char[enoughSpace(prefixLen, column - prefixLen)]; 941 System.arraycopy(this.prefix, 0, prefix, 0, current); 942 this.prefix = prefix; 943 } 944 if (column > current) 945 { 946 for (int i = current; i < column; i++) 947 prefix[i] = ' '; 948 } 949 blocks[blockDepth + BLOCK_PREFIX_LENGTH] = column; 950 } 951 reallyEndLogicalBlock()952 void reallyEndLogicalBlock () 953 { 954 int oldIndent = getPrefixLength(); 955 blockDepth -= LOGICAL_BLOCK_LENGTH; // Pop 956 int newIndent = getPrefixLength(); 957 if (newIndent > oldIndent) 958 { 959 for (int i = oldIndent; i < newIndent; i++) 960 prefix[i] = ' '; 961 } 962 } 963 964 /** 965 * Enqueue a formatting token into {@link #queueInts}. 966 * 967 * The kind and size parameters are packed into a 32-bit integer and then stored in the 968 * QITEM_TYPE_AND_SIZE offset of this token's computed base address. If the 969 * {@link #queueInts} array is not big enough to hold the new token, two cases 970 * arise. If we're not handling a shared structure, the queue is expanded, and 971 * the old formatting tokens are moved to the high end of the queue, leaving 972 * new space at the front. If we are handling shared structure, the queue 973 * size is double, and we continue to enqueue items from the old queueSize. 974 * 975 * @param kind The type of formatting token 976 * @param size The size of this formatting token 977 * @return The address of the token in {@link #queueInts} 978 */ enqueue(int kind, int size)979 public int enqueue (int kind, int size) 980 { 981 int oldLength = queueInts.length; 982 int endAvail = oldLength - queueTail - queueSize; // negative if wrapped 983 // If there are 5 int's left at the end of the queue, and we need to 984 // enqueue an item of size 7, then we don't "bend" the item around the 985 // queue, we just place a dummy formatting token that consumes the rest 986 // of this end, and start again from the beginning. 987 if (endAvail > 0 && size > endAvail && !sharing) 988 enqueue(QITEM_NOP_TYPE, endAvail); 989 if (endAvail < size && (sharing || oldLength - queueSize < size)) 990 { 991 int enough = enoughSpace(oldLength, size); 992 int newLength = enough; 993 994 int[] newInts = new int[newLength]; 995 String[] newStrings = new String[newLength]; 996 997 if (sharing) // then do a linear array expansion 998 { 999 //log("sharing expand: oldLength="+oldLength+" newLength="+newLength+" queueTail="+queueTail); 1000 System.arraycopy(queueInts, 0, newInts, 0, queueInts.length); 1001 System.arraycopy(queueStrings, 0, newStrings, 0, queueStrings.length); 1002 } 1003 else // shift the old items to the top, and insert from the front of a doubled array 1004 { 1005 //log("non-sharing expand: oldLength="+oldLength+" newLength="+newLength+" queueTail="+queueTail); 1006 1007 int queueHead = queueTail + queueSize - oldLength; 1008 if (queueHead > 0) 1009 { // Wraps around. 1010 System.arraycopy(queueInts, 0, newInts, 0, queueHead); 1011 System.arraycopy(queueStrings, 0, newStrings, 0, queueHead); 1012 } 1013 int part1Len = oldLength - queueTail; 1014 int deltaLength = newLength - oldLength; 1015 System.arraycopy(queueInts, queueTail, 1016 newInts, queueTail + deltaLength, 1017 part1Len); 1018 System.arraycopy(queueStrings, queueTail, 1019 newStrings, queueTail + deltaLength, 1020 part1Len); 1021 1022 if (currentBlock >= queueTail) 1023 currentBlock += deltaLength; 1024 queueTail += deltaLength; 1025 } 1026 queueInts = newInts; 1027 queueStrings = newStrings; 1028 } 1029 int addr = queueTail + queueSize; 1030 // Wrap around if we're using a circular queue 1031 if (!sharing && addr >= queueInts.length) 1032 addr -= queueInts.length; 1033 queueInts[addr + QITEM_TYPE_AND_SIZE] = kind | (size << 16); 1034 if (size > 1) // then it's not a NOP, so needs a position 1035 queueInts[addr + QITEM_POSN] = indexPosn(bufferFillPointer); 1036 //log("enqueue "+itemKindString(kind)+" size:"+size+" at:"+queueSize+enqueueExtraLog); enqueueExtraLog = ""; 1037 queueSize += size; 1038 return addr; 1039 } 1040 1041 /** 1042 * Enqueue a newline formatting token. 1043 * 1044 * A pass is made through the formatting tokens up to this one, where section 1045 * sizes are computed. If we're not sharing, then a tentative output is done 1046 * when the current buffer cannot fit on one line. 1047 * @param kind The type of newline to enqueue 1048 */ enqueueNewline(int kind)1049 private void enqueueNewline (int kind) 1050 { 1051 wordEndSeen = false; 1052 int depth = pendingBlocksCount; 1053 //enqueueExtraLog = " kind:"+(char) kind; 1054 int newline = enqueue(QITEM_NEWLINE_TYPE, QITEM_NEWLINE_SIZE); 1055 queueInts[newline + QITEM_NEWLINE_KIND] = kind; 1056 queueInts[newline + QITEM_SECTION_START_DEPTH] = pendingBlocksCount; 1057 queueInts[newline + QITEM_SECTION_START_SECTION_END] = 0; 1058 int entry = queueTail; 1059 int todo = queueSize; 1060 while (todo > 0) 1061 { 1062 if (entry == queueInts.length) 1063 entry = 0; 1064 if (entry == newline) 1065 break; 1066 int type = getQueueType(entry); 1067 if ((type == QITEM_NEWLINE_TYPE 1068 || type == QITEM_BLOCK_START_TYPE) 1069 && queueInts[entry + QITEM_SECTION_START_SECTION_END] == 0 1070 && depth <= queueInts[entry + QITEM_SECTION_START_DEPTH]) 1071 { 1072 int delta = newline - entry; 1073 if (delta < 0) 1074 delta += queueInts.length; 1075 queueInts[entry + QITEM_SECTION_START_SECTION_END] = delta; 1076 } 1077 int size = getQueueSize(entry); 1078 todo -= size; 1079 entry += size; 1080 } 1081 if (!sharing) //!! 1082 maybeOutput (kind == NEWLINE_LITERAL || kind == NEWLINE_MANDATORY || kind == NEWLINE_DUMMY, false); 1083 } 1084 writeBreak(int kind)1085 public final void writeBreak(int kind) 1086 { 1087 if (prettyPrintingMode > 0 || sharing) //!! 1088 enqueueNewline(kind); 1089 } 1090 1091 /** Write a new-line iff the containing section cannot be printed 1092 * on one line. Either all linear-style newlines in a logical 1093 * block becomes spaces (if it all fits in a line), or none 1094 * of them do. */ writeBreakLinear()1095 public void writeBreakLinear() 1096 { 1097 writeBreak(PrettyWriter.NEWLINE_LINEAR); 1098 } 1099 enqueueIndent(char kind, int amount)1100 public int enqueueIndent (char kind, int amount) 1101 { 1102 //enqueueExtraLog = " kind:"+kind+" amount:"+amount; 1103 int result = enqueue(QITEM_INDENTATION_TYPE, QITEM_INDENTATION_SIZE); 1104 queueInts[result + QITEM_INDENTATION_KIND] = kind; 1105 queueInts[result + QITEM_INDENTATION_AMOUNT] = amount; 1106 return result; 1107 } 1108 addIndentation(int amount, boolean current)1109 public void addIndentation(int amount, boolean current) 1110 { 1111 if (prettyPrintingMode > 0) 1112 enqueueIndent((current ? QITEM_INDENTATION_CURRENT 1113 : QITEM_INDENTATION_BLOCK), 1114 amount); 1115 } 1116 startLogicalBlock(String prefix, boolean perLine, String suffix)1117 public void startLogicalBlock (String prefix, boolean perLine, String suffix) 1118 { 1119 // If the queue is empty, it is a good time to check if line-length etc 1120 // have been changed. 1121 if (queueSize == 0 && bufferFillPointer == 0) 1122 { 1123 Object llen = lineLengthLoc.get(null); 1124 if (llen == null) 1125 lineLength = 80; 1126 else 1127 lineLength = Integer.parseInt(llen.toString()); 1128 1129 Object mwidth = miserWidthLoc.get(null); 1130 if (mwidth == null || mwidth == Boolean.FALSE 1131 // For Common Lisp nil. Should we use Language.isTrue() FIXME. 1132 || mwidth == LList.Empty) 1133 miserWidth = -1; 1134 else 1135 miserWidth = Integer.parseInt(mwidth.toString()); 1136 1137 Object indent = indentLoc.get(null); 1138 // if (indent == null || indent ... 1139 } 1140 if (prefix != null && ! (perLine && isDomTerm())) 1141 write(prefix); 1142 if (prettyPrintingMode == 0) 1143 return; 1144 int start = enqueue (QITEM_BLOCK_START_TYPE, 1145 QITEM_BLOCK_START_SIZE); 1146 queueInts[start + QITEM_SECTION_START_DEPTH] = pendingBlocksCount; 1147 queueStrings[start + QITEM_BLOCK_START_PREFIX] 1148 = perLine ? prefix : null; 1149 queueStrings[start + QITEM_BLOCK_START_SUFFIX] = suffix; 1150 pendingBlocksCount++; 1151 int outerBlock = currentBlock; 1152 if (outerBlock < 0) 1153 outerBlock = 0; 1154 else 1155 { 1156 outerBlock -= start; 1157 if (outerBlock > 0) 1158 outerBlock -= queueInts.length; 1159 } 1160 queueInts[start + QITEM_BLOCK_START_BLOCK_END] = outerBlock; 1161 queueInts[start + QITEM_SECTION_START_SECTION_END] = 0; 1162 currentBlock = start; 1163 } 1164 startLogicalBlock(String prefix, String suffix, int indent)1165 public void startLogicalBlock(String prefix, String suffix, 1166 int indent) { 1167 startLogicalBlock(prefix, false, suffix); 1168 addIndentation(prefix == null ? indent 1169 : indent - prefix.length(), 1170 false); 1171 } 1172 endLogicalBlock()1173 public void endLogicalBlock () 1174 { 1175 int end = enqueue (QITEM_BLOCK_END_TYPE, QITEM_BLOCK_END_SIZE); 1176 pendingBlocksCount--; 1177 if (currentBlock < 0) 1178 { 1179 // reallyStartLogicalBlock has been called for the matching 1180 // BEGIN_BLOCK, so it is no longer in the queue. Instead it is in 1181 // the 'blocks' stack. 1182 int suffixLength = blocks[blockDepth+BLOCK_SUFFIX_LENGTH]; 1183 int suffixPreviousLength 1184 = blocks[blockDepth - LOGICAL_BLOCK_LENGTH + BLOCK_SUFFIX_LENGTH]; 1185 if (suffixLength > suffixPreviousLength) 1186 write(this.suffix, 1187 this.suffix.length - suffixLength, 1188 suffixLength - suffixPreviousLength); 1189 currentBlock = -1; 1190 return; 1191 } 1192 int start = currentBlock; 1193 int outerBlock = queueInts[start + QITEM_BLOCK_START_BLOCK_END]; 1194 if (outerBlock == 0) 1195 currentBlock = -1; 1196 else 1197 { 1198 int qtailFromStart = queueTail - start; 1199 if (qtailFromStart > 0) 1200 qtailFromStart -= queueInts.length; 1201 if (outerBlock < qtailFromStart) 1202 { 1203 // reallyStartLogicalBlock has been called for the outer block, 1204 // so there is no currentBlock. 1205 currentBlock = -1; 1206 } 1207 else 1208 { 1209 // Make currentBlock absolute instead of relative. 1210 outerBlock += start; 1211 if (outerBlock < 0) 1212 outerBlock += queueInts.length; 1213 currentBlock = outerBlock; 1214 } 1215 } 1216 String suffix = queueStrings[start + QITEM_BLOCK_START_SUFFIX]; 1217 if (suffix != null) 1218 write(suffix); 1219 int endFromStart = end - start; 1220 if (endFromStart < 0) // wrap-around. 1221 endFromStart += queueInts.length; 1222 queueInts[start + QITEM_BLOCK_START_BLOCK_END] = endFromStart; 1223 //log("endLogicalBlock end:"+end+" start:"+start+" rel:"+endFromStart); 1224 } 1225 endLogicalBlock(String suffix)1226 public void endLogicalBlock (String suffix) 1227 { 1228 if (prettyPrintingMode > 0) 1229 endLogicalBlock(); 1230 else if (suffix != null) 1231 write(suffix); 1232 } 1233 1234 // Tab support 1235 computeTabSize(int tab, int sectionStart, int column)1236 int computeTabSize (int tab, int sectionStart, int column) // DONE 1237 { 1238 int flags = queueInts[tab + QITEM_TAB_FLAGS]; 1239 boolean isSection = (flags & QITEM_TAB_IS_SECTION) != 0; 1240 boolean isRelative = (flags & QITEM_TAB_IS_RELATIVE) != 0; 1241 int origin = isSection ? sectionStart : 0; 1242 int colnum = queueInts[tab + QITEM_TAB_COLNUM]; 1243 int colinc = queueInts[tab + QITEM_TAB_COLINC]; 1244 if (isRelative) 1245 { 1246 if (colinc > 1) 1247 { 1248 int newposn = column + colnum; 1249 int rem = newposn % colinc; 1250 if (rem != 0) 1251 colnum += colinc = rem; 1252 } 1253 return colnum; 1254 } 1255 else if (column <= colnum + origin) 1256 return column + origin - column; 1257 else 1258 return colinc - (column - origin) % colinc; 1259 } 1260 indexColumn(int index)1261 int indexColumn(int index) 1262 { 1263 int column = bufferStartColumn; 1264 int sectionStart = getSectionColumn(); 1265 int endPosn = indexPosn(index); 1266 int op = queueTail; 1267 int todo = queueSize; 1268 while (todo > 0) 1269 { 1270 // If at end of queueInts, skip. 1271 if (op >= queueInts.length) 1272 op = 0; 1273 int type = getQueueType(op); 1274 if (type != QITEM_NOP_TYPE) 1275 { 1276 int posn = queueInts[op + QITEM_POSN]; 1277 if (posn >= endPosn) 1278 break; 1279 if (type == QITEM_TAB_TYPE) 1280 column += computeTabSize(op, sectionStart, 1281 column + posnIndex (posn)); 1282 else if (type == QITEM_NEWLINE_TYPE 1283 || type == QITEM_BLOCK_START_TYPE) 1284 sectionStart 1285 = column + posnIndex(queueInts[op + QITEM_POSN]); 1286 } 1287 int size = getQueueSize(op); 1288 todo -= size; 1289 op += size; 1290 } 1291 return column + index; 1292 } 1293 expandTabs(int through)1294 void expandTabs (int through) 1295 { 1296 int numInsertions = 0; 1297 int additional = 0; 1298 int column = bufferStartColumn; 1299 int sectionStart = getSectionColumn(); 1300 int op = queueTail; 1301 int todo = queueSize; 1302 int blocksUsed = LOGICAL_BLOCK_LENGTH * pendingBlocksCount; 1303 while (todo > 0) 1304 { 1305 if (op == queueInts.length) 1306 op = 0; 1307 if (op == through) 1308 break; 1309 int type = getQueueType(op); 1310 if (type == QITEM_TAB_TYPE) 1311 { 1312 int index = posnIndex(queueInts[op + QITEM_POSN]); 1313 int tabsize = computeTabSize (op, sectionStart, column + index); 1314 if (tabsize != 0) 1315 { 1316 // We use the blocks array for a temporary tab buffer. 1317 if (blocksUsed + 2 * numInsertions + 1 >= blocks.length) 1318 { 1319 int[] newBlocks = new int[2 * blocks.length]; 1320 System.arraycopy(blocks, 0, newBlocks, 0, blocks.length); 1321 blocks = newBlocks; 1322 } 1323 blocks[blocksUsed + 2 * numInsertions] = index; 1324 blocks[blocksUsed + 2 * numInsertions + 1] = tabsize; 1325 numInsertions++; 1326 additional += tabsize; 1327 column += tabsize; 1328 } 1329 } 1330 else if (op == QITEM_NEWLINE_TYPE || op == QITEM_BLOCK_START_TYPE) 1331 { 1332 sectionStart = column + posnIndex(queueInts[op + QITEM_POSN]); 1333 } 1334 int size = getQueueSize(op); 1335 todo -= size; 1336 op += size; 1337 } 1338 if (numInsertions > 0) 1339 { 1340 int fillPtr = bufferFillPointer; 1341 int newFillPtr = fillPtr + additional; 1342 char[] buffer = this.buffer; 1343 char[] newBuffer = buffer; 1344 int length = buffer.length; 1345 int end = fillPtr; 1346 if (newFillPtr > length) 1347 { 1348 int newLength = enoughSpace (fillPtr, additional); 1349 newBuffer = new char[newLength]; 1350 this.buffer = newBuffer; 1351 } 1352 bufferFillPointer = newFillPtr; 1353 bufferOffset -= additional; 1354 for (int i = numInsertions; --i >= 0; ) 1355 { 1356 int srcpos = blocks[blocksUsed + 2 * i]; 1357 int amount = blocks[blocksUsed + 2 * i + 1]; 1358 int dstpos = srcpos + additional; 1359 System.arraycopy(buffer, srcpos, newBuffer, dstpos, end - srcpos); 1360 for (int j = dstpos - amount; j < dstpos; j++) 1361 newBuffer[j] = ' '; 1362 additional -= amount; 1363 end = srcpos; 1364 } 1365 if (newBuffer != buffer) 1366 System.arraycopy(buffer, 0, newBuffer, 0, end); 1367 } 1368 } 1369 1370 // stuff to do the actual outputting 1371 1372 /** 1373 * Make sure we can write into the buffer without overflowing it. 1374 * 1375 * If the current {@link #bufferFillPointer} is greater than the line length, 1376 * do a tentative output. 1377 * @param want How much space we need 1378 * @return The amount of space available, possible after flushes or expansion 1379 */ ensureSpaceInBuffer(int want)1380 int ensureSpaceInBuffer (int want) 1381 { 1382 char[] buffer = this.buffer; 1383 int length = buffer.length; 1384 int fillPtr = bufferFillPointer; 1385 int available = length - fillPtr; 1386 if (available > 0) 1387 return available; 1388 else if (out != null && fillPtr > lineLength && !sharing && ! isDomTerm()) 1389 { 1390 if (! maybeOutput(false, false)) 1391 outputPartialLine(); 1392 return ensureSpaceInBuffer(want); 1393 } 1394 else 1395 { 1396 int newLength = enoughSpace(length, want); 1397 char[] newBuffer = new char[newLength]; 1398 this.buffer = newBuffer; 1399 for (int i = fillPtr; --i >= 0; ) 1400 newBuffer[i] = buffer[i]; 1401 return newLength - fillPtr; 1402 } 1403 } 1404 1405 /** 1406 * 1407 * @param forceNewlines is true if we're printing a "literal" newline or if 1408 * the printer has decided that we need to force a 1409 * newline for a sensible formatting. 1410 * @param flushing is true when the buffer is actually written to the output 1411 * port. This occurs on {@link #flush()} operations for instance. 1412 * @return true if something has been output, false otherwise. 1413 */ maybeOutput(boolean forceNewlines, boolean flushing)1414 boolean maybeOutput(boolean forceNewlines, boolean flushing) 1415 { 1416 boolean outputAnything = false; 1417 //log("maybeOutput("+forceNewlines+","+flushing+"):"); dumpQueue(); 1418 loop: 1419 while (queueSize > 0) 1420 { 1421 if (queueTail >= queueInts.length) 1422 queueTail = 0; 1423 int next = queueTail; 1424 int type = getQueueType(next); 1425 if (isDomTerm()) { 1426 if (bufferFillPointer > 0 && type != QITEM_NEWLINE_TYPE && type != QITEM_NOP_TYPE) { 1427 int count = posnIndex(queueInts[next+QITEM_POSN]); 1428 if (count > 0) { 1429 writeToBase(buffer, 0, count); 1430 System.arraycopy(buffer, count, buffer, 0, bufferFillPointer-count); 1431 bufferOffset += count; 1432 bufferFillPointer -= count; 1433 bufferStartColumn += count; 1434 } 1435 } 1436 switch (type) 1437 { 1438 case QITEM_NEWLINE_TYPE: 1439 outputAnything = true; 1440 outputLine(next, flushing); 1441 break; 1442 case QITEM_INDENTATION_TYPE: 1443 if (! isMisering()) 1444 { 1445 int kind = queueInts[next+QITEM_INDENTATION_KIND]; 1446 int indent = queueInts[next+QITEM_INDENTATION_AMOUNT]; 1447 boolean blockRelative = kind == QITEM_INDENTATION_BLOCK; 1448 writeToBase("\033]" 1449 +(blockRelative ? "113" : "112") 1450 + ";" + indent + "\007"); 1451 } 1452 break; 1453 case QITEM_BLOCK_START_TYPE: 1454 String prefix = queueStrings[next + QITEM_BLOCK_START_PREFIX]; 1455 StringBuilder sbuf = new StringBuilder("\033]110;"); 1456 if (prefix != null) 1457 Strings.printJson(prefix, sbuf); 1458 sbuf.append("\007"); 1459 writeToBase(sbuf.toString()); 1460 String suffix = queueStrings[next + QITEM_BLOCK_START_SUFFIX]; 1461 reallyStartLogicalBlock (posnColumn(queueInts[next + QITEM_POSN]), 1462 null, suffix); 1463 if (currentBlock == next) 1464 currentBlock = -1; 1465 break; 1466 case QITEM_BLOCK_END_TYPE: 1467 writeToBase("\033]111\007"); 1468 blockDepth -= LOGICAL_BLOCK_LENGTH; // Pop 1469 break; 1470 case QITEM_TAB_TYPE: 1471 // if (isDomTerm()) ??? FIXME 1472 expandTabs(next); 1473 break; 1474 } 1475 } 1476 else // ! isDomTerm() 1477 switch (type) 1478 { 1479 case QITEM_NEWLINE_TYPE: 1480 boolean cond; 1481 int fits = -1; 1482 switch (queueInts[next+QITEM_NEWLINE_KIND]) 1483 { 1484 default: // LINEAR, LITERAL, DUMMY, or MANDATORY: 1485 cond = true; 1486 break; 1487 case NEWLINE_MISER: 1488 cond = isMisering(); 1489 break; 1490 case NEWLINE_FILL: 1491 if (isMisering() 1492 || (lineNumber > getSectionStartLine())) 1493 { 1494 cond = true; 1495 break; 1496 } 1497 /// ... fall through to ... 1498 case NEWLINE_SPACE: 1499 int end = queueInts[next+QITEM_SECTION_START_SECTION_END]; 1500 if (end == 0) 1501 end = -1; 1502 else 1503 { // convert relative->absolute. 1504 end = next + end; 1505 if (end >= queueInts.length) 1506 end -= queueInts.length; 1507 } 1508 fits = fitsOnLine(end, forceNewlines); 1509 if (fits > 0) 1510 cond = false; 1511 else if (fits < 0 || flushing) 1512 cond = true; 1513 else 1514 break loop; 1515 break; 1516 } 1517 if (cond) 1518 { 1519 outputAnything = true; 1520 if (flushing && fits == 0) 1521 outputPartialLine(); 1522 else 1523 outputLine(next, flushing); 1524 } 1525 break; 1526 case QITEM_INDENTATION_TYPE: 1527 if (! isMisering()) 1528 { 1529 int kind = queueInts[next+QITEM_INDENTATION_KIND]; 1530 int indent = queueInts[next+QITEM_INDENTATION_AMOUNT]; 1531 if (kind == QITEM_INDENTATION_BLOCK) 1532 indent += getStartColumn(); 1533 else 1534 indent += posnColumn(queueInts[next+QITEM_POSN]); 1535 //log("setIndent from "+next+": "+queueInts[next+QITEM_INDENTATION_AMOUNT]+" column:"+indent); 1536 setIndentation(indent); 1537 } 1538 break; 1539 case QITEM_BLOCK_START_TYPE: 1540 int start = next; 1541 int end = queueInts[next + QITEM_SECTION_START_SECTION_END]; 1542 // Convert relative offset to absolute index: 1543 end = end > 0 ? (end + next) % queueInts.length : -1; 1544 fits = fitsOnLine (end, forceNewlines); 1545 //log("block-start @"+next+" end:"+end+" force:"+forceNewlines+" fits:"+fits); 1546 if (fits > 0) 1547 { 1548 // Just nuke the whole logical block and make it look 1549 // like one nice long literal. 1550 int endr = queueInts[next + QITEM_BLOCK_START_BLOCK_END]; 1551 // make absolute: 1552 next = (endr + next) % queueInts.length; 1553 expandTabs(next); 1554 queueTail = next; 1555 queueSize -= endr; 1556 //log("remove block -> next:"+next+" endr:"+endr+" qSize:"+queueSize); 1557 } 1558 else if (fits < 0 || flushing) 1559 { 1560 String prefix = queueStrings[next + QITEM_BLOCK_START_PREFIX]; 1561 String suffix = queueStrings[next + QITEM_BLOCK_START_SUFFIX]; 1562 //log("reallyStartLogicalBlock: "+blockDepth+" at:"+next); 1563 reallyStartLogicalBlock (posnColumn(queueInts[next + QITEM_POSN]), 1564 prefix, suffix); 1565 } 1566 else // Don't know. 1567 break loop; 1568 if (currentBlock == start) 1569 currentBlock = -1; 1570 break; 1571 case QITEM_BLOCK_END_TYPE: 1572 //log("reallyEndLogicalBlock: "+blockDepth+" at:"+next); 1573 reallyEndLogicalBlock(); 1574 break; 1575 case QITEM_TAB_TYPE: 1576 expandTabs(next); 1577 break; 1578 } 1579 int size = getQueueSize(queueTail); 1580 queueSize -= size; 1581 //log("maybeWrite size: "+size+" ->"+queueSize); 1582 queueTail = next + size; 1583 } 1584 return outputAnything; 1585 } 1586 getMiserWidth()1587 protected int getMiserWidth () // DONE 1588 { 1589 // CommonLisp: Use *print-miser-width*. 1590 return miserWidth; 1591 } 1592 isMisering()1593 boolean isMisering() // DONE 1594 { 1595 int mwidth = getMiserWidth (); 1596 return (mwidth > 0 1597 && lineLength - getStartColumn() <= mwidth); 1598 } 1599 getMaxLines()1600 int getMaxLines () 1601 { 1602 // Should be value of CommonLisp *print-lines*. 1603 return -1; 1604 } 1605 printReadably()1606 boolean printReadably() 1607 { 1608 // Should be value of CommonLisp *print-readably*. 1609 return true; 1610 } 1611 1612 /** Return 1 if true; -1 if false; 0 if don't know. */ fitsOnLine(int sectionEnd, boolean forceNewlines)1613 int fitsOnLine (int sectionEnd, boolean forceNewlines) // DONE 1614 { 1615 int available = lineLength; 1616 if (! printReadably() && getMaxLines() == lineNumber) 1617 { 1618 available -= 3; // For the " ..". 1619 available -= getSuffixLength(); 1620 } 1621 if (sectionEnd >= 0) 1622 return posnColumn(queueInts[sectionEnd + QITEM_POSN]) <= available ? 1 : -1; 1623 if (forceNewlines) 1624 return -1; 1625 if (indexColumn(bufferFillPointer) > available) 1626 return -1; 1627 return 0; // don't know. 1628 } 1629 lineAbbreviationHappened()1630 public void lineAbbreviationHappened() 1631 { 1632 // Hook. 1633 } 1634 writeBreak(int kind, boolean space)1635 private void writeBreak(int kind, boolean space) { 1636 String cmd = null; 1637 int op = -1; 1638 switch (kind) { 1639 case NEWLINE_FILL: 1640 case NEWLINE_SPACE: 1641 op = 115; break; 1642 case NEWLINE_LINEAR: 1643 op = 116; break; 1644 case NEWLINE_MISER: 1645 op = 117; break; 1646 case NEWLINE_LITERAL: 1647 if (blockDepth == LOGICAL_BLOCK_LENGTH) { 1648 cmd = "\n"; 1649 break; 1650 } 1651 // else fall through 1652 default: 1653 op = 118; break; 1654 } 1655 if (op >= 0) 1656 cmd = "\033]"+op+(space ? ";\"\",\"\",\" \"\007" : "\007"); 1657 writeToBase(cmd); 1658 } 1659 1660 /** Output a new line. 1661 * @param newline index of a newline queue item 1662 */ outputLine(int newline, boolean flushing)1663 private void outputLine (int newline, boolean flushing) 1664 { 1665 //log("outputLine newline:"+newline+" flushing:"+flushing); 1666 char[] buffer = this.buffer; 1667 int kind = queueInts[newline + QITEM_NEWLINE_KIND]; 1668 boolean isLiteral = kind == NEWLINE_LITERAL; 1669 int amountToConsume = posnIndex(queueInts[newline + QITEM_POSN]); 1670 int amountToPrint; 1671 if (isLiteral || kind == NEWLINE_DUMMY || isDomTerm()) 1672 amountToPrint = amountToConsume; 1673 else 1674 { 1675 // Skip trailing spaces. 1676 for (int i = amountToConsume; ; ) 1677 { 1678 if (--i < 0) 1679 { 1680 amountToPrint = 0; 1681 break; 1682 } 1683 if (buffer[i] != ' ') 1684 { 1685 amountToPrint = i + 1; 1686 break; 1687 } 1688 } 1689 } 1690 boolean spaceBreak = false; 1691 if (kind != NEWLINE_DUMMY && isDomTerm() 1692 && amountToPrint > 0 && buffer[amountToPrint-1] == ' ') { 1693 amountToPrint--; 1694 spaceBreak = true; 1695 } 1696 writeToBase(buffer, 0, amountToPrint); 1697 int lineNumber = this.lineNumber; 1698 lineNumber++; 1699 if (! printReadably()) 1700 { 1701 int maxLines = getMaxLines(); 1702 if (maxLines > 0 && lineNumber >= maxLines) 1703 { 1704 writeToBase(" .."); 1705 int suffixLength = getSuffixLength(); 1706 if (suffixLength != 0) 1707 { 1708 char[] suffix = this.suffix; 1709 int len = suffix.length; 1710 writeToBase(suffix, len - suffixLength, suffixLength); 1711 } 1712 // (throw 'line-limit-abbreviation-happened t)) 1713 lineAbbreviationHappened(); 1714 } 1715 } 1716 this.lineNumber = lineNumber; 1717 if (kind == NEWLINE_DUMMY) { 1718 // emit nothing 1719 } else if (isDomTerm()) { 1720 writeBreak(kind, spaceBreak); 1721 } else { 1722 writeToBase('\n'); 1723 } 1724 bufferStartColumn = kind != NEWLINE_DUMMY ? 0 : getColumnNumber(); 1725 int fillPtr = bufferFillPointer; 1726 int prefixLen = isLiteral ? getPerLinePrefixEnd() : getPrefixLength(); 1727 int shift = amountToConsume - prefixLen; 1728 int newFillPtr = fillPtr - shift; 1729 char[] newBuffer = buffer; 1730 int bufferLength = buffer.length; 1731 if (newFillPtr > bufferLength) 1732 { 1733 newBuffer = new char[enoughSpace(bufferLength, 1734 newFillPtr - bufferLength)]; 1735 this.buffer = newBuffer; 1736 } 1737 System.arraycopy(buffer, amountToConsume, newBuffer, prefixLen, 1738 fillPtr - amountToConsume); 1739 System.arraycopy(prefix, 0, newBuffer, 0, prefixLen); 1740 bufferFillPointer = newFillPtr; 1741 bufferOffset += shift; 1742 if (! isLiteral) 1743 { 1744 blocks[blockDepth+BLOCK_SECTION_COLUMN] = prefixLen; 1745 blocks[blockDepth+BLOCK_SECTION_START_LINE] = lineNumber; 1746 } 1747 } 1748 outputPartialLine()1749 void outputPartialLine () 1750 { 1751 //log("outputPartialLine"); 1752 int tail = queueTail; 1753 while (queueSize > 0 && getQueueType(tail) == QITEM_NOP_TYPE) 1754 { 1755 int size = getQueueSize(tail); 1756 queueSize -= size; 1757 tail += size; 1758 if (tail == queueInts.length) 1759 tail = 0; 1760 queueTail = tail; 1761 } 1762 int fillPtr = bufferFillPointer; 1763 int count = queueSize > 0 ? posnIndex (queueInts[tail + QITEM_POSN]) 1764 : fillPtr; 1765 int newFillPtr = fillPtr - count; 1766 if (count <= 0) 1767 throw new Error("outputPartialLine called when nothing can be output."); 1768 writeToBase(buffer, 0, count); 1769 bufferFillPointer = count; // For the sake of the following: 1770 bufferStartColumn = getColumnNumber(); 1771 System.arraycopy(buffer, count, buffer, 0, newFillPtr); 1772 bufferFillPointer = newFillPtr; 1773 bufferOffset += count; 1774 } 1775 1776 /** 1777 * Walk through the queue, emitting print circle notation as described in 1778 * SRFI-38. 1779 * 1780 * For each position marker encountered, if the position marker 1781 * has been referenced, emit the #N= notation. For each back-reference, find 1782 * the corresponding position marker and emit the #N# notation. A Gap Buffer 1783 * is employed to facilitate amortised linear buffer insertion when emitting 1784 * the new print-circle notation. The method populates the rest of the buffer 1785 * contents by using the QITEM_POSN information in SRFI-38 unrelated formatting 1786 * tokens. When the print-circle notation has been emitted, future QITEM_POSN's 1787 * need to be updated, this is achieved by maintaining a delta variable that 1788 * records how many extra characters have been emitted to the output buffer. 1789 */ resolveBackReferences()1790 public void resolveBackReferences () 1791 { 1792 if (!reallySharing) 1793 return; 1794 int posnMarkerCount = 0; 1795 GapBuffer gbuffer = new GapBuffer(buffer, buffer.length); 1796 // The delta variable records how many extra characters have been emitted 1797 // to the {@link #buffer}. 1798 int delta = 0; 1799 // Used to compute the relative delta of an SRFI-38 specific token. 1800 int oldDelta; 1801 int relativeAddress; 1802 1803 //log ("resolveBackReferences: Starting at tail = "+queueTail); 1804 int tail = queueTail; 1805 int todo = queueSize; 1806 while (todo > 0) 1807 { 1808 if (tail >= queueInts.length) // then wrap around 1809 tail = 0; 1810 1811 int next = tail; 1812 int type = getQueueType(next); 1813 1814 // The QITEM_POSN offset will return the position this QITEM occupies 1815 // in the output buffer. Since this QITEM is not SRFI-38 specific, the 1816 // printer must output all characters up to this token into the new 1817 // buffer. 1818 if (type != QITEM_NOP_TYPE) 1819 gbuffer.addUpTo(posnIndex(queueInts[next + QITEM_POSN])); 1820 1821 switch (type) 1822 { 1823 case QITEM_POSNMARKER_TYPE: 1824 oldDelta = delta; 1825 // A position marker is active if it has been back-referenced. 1826 if (posnMarkerActive(next)) 1827 { 1828 if (posnMarkerIsGrouping(next)) 1829 { 1830 gbuffer.add('.'); 1831 gbuffer.add(' '); 1832 delta += 2; 1833 } 1834 gbuffer.add('#'); 1835 // Set this position markers reference count if it hasn't been set 1836 // before. Note: all position marker default to a count of one. 1837 if (queueInts[next + QITEM_POSNMARKER_LOC] == 1) 1838 { 1839 queueInts[next + QITEM_POSNMARKER_LOC] = posnMarkerCount++; 1840 } 1841 // The index that the referenced position marker resides in 1842 int reference = queueInts[next + QITEM_POSNMARKER_LOC]; 1843 // The reference could be of arbitrary magnitude, it's typically 1844 // a unit digit however. 1845 delta += gbuffer.add(reference); 1846 gbuffer.add('='); 1847 delta += 2; // For the '#' and '=' characters 1848 1849 if (posnMarkerIsGrouping(next)) 1850 { 1851 gbuffer.add('('); 1852 delta++; 1853 // XXX Should we start a new logical block? Doesn't look great if 1854 // you try it... 1855 } 1856 1857 // Compute the *relative* position of this QITEM 1858 //log("POSN-MARKER: queueInts[" + next + " + " + QITEM_POSN + "] = " + (delta - oldDelta)); 1859 queueInts[next + QITEM_POSN] += delta - oldDelta; 1860 } 1861 break; 1862 case QITEM_BACKREF_TYPE: 1863 oldDelta = delta; 1864 // The index that the referenced position marker resides in 1865 relativeAddress = queueInts[next + QITEM_BACKREF_TARGET]; 1866 //log("RESOLVE-BACKREF: rel=" + relativeAddress + " @:" + next + "(" + (next + relativeAddress) + ")"); 1867 // The count refers to the N digit in #N= notation. Starts at one. 1868 int count = queueInts[relativeAddress + QITEM_POSNMARKER_LOC]; 1869 gbuffer.add('#'); 1870 // The reference could be of arbitrary magnitude, it's typically 1871 // a unit digit however. 1872 delta += gbuffer.add(count); 1873 gbuffer.add('#'); 1874 delta += 2; // For the '=' and '#' characters 1875 // New characters have been added, so this items output position must 1876 // change. 1877 queueInts[next + QITEM_POSN] += delta - oldDelta; 1878 break; 1879 case QITEM_PAIR_END_TYPE: 1880 relativeAddress = queueInts[next + QITEM_PAIR_END_REF]; 1881 //log("RESOLVE-PAIREND: rel=" + relativeAddress + "@:" + next + "(" + (next + relativeAddress) + ")"); 1882 if (posnMarkerActive(relativeAddress)) 1883 { 1884 gbuffer.add(')'); 1885 delta++; 1886 queueInts[next + QITEM_POSN] += 1; 1887 } 1888 break; 1889 default: 1890 // Now update the token position information with the current delta. 1891 queueInts[next + QITEM_POSN] += delta; 1892 break; 1893 } 1894 int size = getQueueSize(tail); 1895 // "Dequeue" this token 1896 todo -= size; 1897 // Point to the next token 1898 tail = next + size; 1899 } 1900 // The delta variable records how many extra characters have been emitted. 1901 // The bufferFillPointer points to the end of the characters to be flushed 1902 bufferFillPointer += delta; 1903 // The Gap Buffer will remove its gap and present the newly created buffer. 1904 buffer = gbuffer.restoreBuffer(); 1905 posnMarkerCount = 1; 1906 } 1907 forcePrettyOutput()1908 public void forcePrettyOutput () 1909 { 1910 enqueueNewline(NEWLINE_DUMMY); 1911 maybeOutput(false, true); 1912 if (bufferFillPointer > 0) 1913 outputPartialLine(); 1914 expandTabs(-1); 1915 bufferStartColumn = getColumnNumber(); 1916 writeToBase(buffer, 0, bufferFillPointer); 1917 bufferFillPointer = 0; 1918 queueSize = queueTail = bufferOffset = 0; 1919 } 1920 flush()1921 public void flush() 1922 { 1923 if (out == null) 1924 return; 1925 try 1926 { 1927 forcePrettyOutput(); 1928 out.flush(); 1929 } 1930 catch (IOException ex) 1931 { 1932 throw new RuntimeException(ex.toString()); 1933 } 1934 } 1935 close()1936 public void close() 1937 { 1938 if (out != null) 1939 { 1940 forcePrettyOutput(); 1941 super.close(); 1942 out = null; 1943 } 1944 buffer = null; 1945 } 1946 1947 /** Flush and close this local Writer, but not underlying Writers. */ closeThis()1948 public void closeThis() throws IOException 1949 { 1950 if (out != null) 1951 { 1952 forcePrettyOutput(); 1953 out = null; 1954 } 1955 buffer = null; 1956 } 1957 atLineStart()1958 public boolean atLineStart() { 1959 // Could/should optimize. 1960 return getColumnNumber() == 0; 1961 } 1962 1963 /** Get zero-origin column number, or -1 if unknown. 1964 * Not meaningful if {@code prettyPrintingMode > 0}. */ getColumnNumber()1965 public int getColumnNumber () 1966 { 1967 int i = bufferFillPointer; 1968 for (;;) 1969 { 1970 if (--i < 0) 1971 return bufferStartColumn + bufferFillPointer; 1972 char ch = buffer[i]; 1973 if (ch == '\n' || ch == '\r') 1974 return bufferFillPointer - (i+1); 1975 } 1976 } 1977 setColumnNumber(int column)1978 public void setColumnNumber (int column) 1979 { 1980 bufferStartColumn += column - getColumnNumber (); 1981 } 1982 clearBuffer()1983 public void clearBuffer () 1984 { 1985 bufferStartColumn = 0; 1986 bufferFillPointer = 0; 1987 lineNumber = 0; 1988 bufferOffset = 0; 1989 blockDepth = LOGICAL_BLOCK_LENGTH; 1990 queueTail = 0; 1991 queueSize = 0; 1992 pendingBlocksCount = 0; 1993 } 1994 1995 private static final class GapBuffer 1996 { 1997 char[] buffer; 1998 char[] existingBuffer; 1999 int point; 2000 int existingIndex; 2001 int gapSize; 2002 GapBuffer(char[] existing, int startSize)2003 public GapBuffer (char[] existing, int startSize) 2004 { 2005 this.buffer = new char[startSize]; 2006 this.point = 0; 2007 this.existingIndex = 0; 2008 this.existingBuffer = existing; 2009 } 2010 2011 /** 2012 * The point represents where new characters will be buffered. 2013 * @return The index that represents where new characters will be buffered. 2014 */ getPoint()2015 public int getPoint () 2016 { 2017 return point; 2018 } 2019 2020 /** 2021 * Add a character to the buffer. 2022 * @param ch The character to add 2023 */ add(char ch)2024 public void add (char ch) 2025 { 2026 if (point + 1 >= buffer.length) 2027 { 2028 expandBuffer(1); 2029 } 2030 buffer[point++] = ch; 2031 } 2032 2033 /** 2034 * Add a non-negative integer to the buffer. 2035 * @param i The non-negative integer to add 2036 * @return The number of digits in the integer 2037 */ add(int i)2038 public int add (int i) 2039 { 2040 int ndigits = 1; 2041 if (i >= 10) 2042 { 2043 ndigits += add(i / 10); 2044 i %= 10; 2045 } 2046 add((char)('0' + i)); 2047 return ndigits; 2048 } 2049 2050 /** 2051 * Copy characters from the existing buffer to the gap buffer. 2052 * @param end the upper index of the characters to copy 2053 */ addUpTo(int end)2054 public void addUpTo(int end) 2055 { 2056 int n = end - existingIndex; 2057 if (point + n >= buffer.length) 2058 { 2059 expandBuffer(n); 2060 } 2061 while (existingIndex < end) 2062 { 2063 buffer[point++] = existingBuffer[existingIndex++]; 2064 } 2065 } 2066 2067 /** 2068 * @return The buffer with the gap removed 2069 */ restoreBuffer()2070 public char[] restoreBuffer () 2071 { 2072 char[] retBuffer = new char[buffer.length]; 2073 System.arraycopy(buffer, 0, retBuffer, 0, point); 2074 return retBuffer; 2075 } 2076 expandBuffer(int n)2077 private void expandBuffer(int n) 2078 { 2079 int newLength = buffer.length; 2080 int minimum = newLength + n; 2081 do 2082 { 2083 newLength <<= 1; 2084 } while (newLength < minimum); 2085 2086 char[] newBuffer = new char[newLength]; 2087 System.arraycopy(buffer, 0, newBuffer, 0, point); 2088 buffer = newBuffer; 2089 } 2090 } 2091 2092 /* 2093 public static PrintWriter log; 2094 static { 2095 try { log = new PrintWriter(new FileOutputStream("/tmp/pplog")); } 2096 catch (Throwable ex) { ex.printStackTrace(); } 2097 } 2098 public static void log(String str) 2099 { 2100 log.println(str); 2101 log.flush(); 2102 } 2103 void dumpQueue() 2104 { 2105 log.println("Queue tail:"+queueTail+" size:"+queueSize 2106 +" length:"+queueInts.length+" startCol:"+bufferStartColumn); 2107 dumpQueue(queueTail, queueSize, log); 2108 } 2109 2110 void dumpQueue(int start, int todo, PrintWriter out) 2111 { 2112 int bufIndex = 0; 2113 while (todo > 0) 2114 { 2115 if (start == queueInts.length) 2116 start = 0; 2117 if (start < 0 || start >= queueInts.length) 2118 { 2119 out.print('@'); out.print(start); out.print(": "); 2120 out.print("out of bounds - queueInts length is "); 2121 out.println(queueInts.length); 2122 break; 2123 } 2124 int type = getQueueType(start); 2125 int size = getQueueSize(start); 2126 if (type != QITEM_NOP_TYPE) 2127 { 2128 int newIndex = posnIndex(queueInts[start+QITEM_POSN]); 2129 int count = newIndex - bufIndex; 2130 if (count > 0) 2131 { 2132 out.print(count); out.print(" chars: \""); 2133 out.write(buffer, bufIndex, count); 2134 out.println('\"'); 2135 bufIndex = newIndex; 2136 } 2137 } 2138 out.print('@'); out.print(start); out.print(": "); 2139 out.print("type:"); out.print(type); 2140 switch (type) 2141 { 2142 case QITEM_NEWLINE_TYPE: 2143 out.print("(newline)"); break; 2144 case QITEM_INDENTATION_TYPE: 2145 out.print("(indentation)"); break; 2146 case QITEM_BLOCK_START_TYPE: 2147 out.print("(block-start)"); break; 2148 case QITEM_BLOCK_END_TYPE: 2149 out.print("(block-end)"); break; 2150 case QITEM_TAB_TYPE: out.print("(tab)"); 2151 break; 2152 case QITEM_POSNMARKER_TYPE: 2153 out.print("(posnmarker)"); break; 2154 case QITEM_BACKREF_TYPE: 2155 out.print("(backref)"); break; 2156 case QITEM_NOP_TYPE: 2157 out.print("(nop)"); break; 2158 } 2159 out.print(" size:"); out.print(size); 2160 out.print("; @"); out.print(start+QITEM_POSN); 2161 if (type != QITEM_NOP_TYPE) 2162 { 2163 out.print(": posn:"); 2164 int posn = queueInts[start+QITEM_POSN]; 2165 out.print(posn); 2166 out.print(" index:"); 2167 out.println(posnIndex(posn)); 2168 } 2169 if (type == QITEM_NEWLINE_TYPE 2170 || type == QITEM_BLOCK_START_TYPE) 2171 2172 { 2173 out.print('@'); out.print(start+QITEM_SECTION_START_DEPTH); 2174 out.print(": - depth:"); 2175 out.print(queueInts[start+QITEM_SECTION_START_DEPTH]); 2176 out.print("; @"); 2177 out.print(start+QITEM_SECTION_START_SECTION_END); 2178 out.print(": section-end:"); 2179 out.println(queueInts[start+QITEM_SECTION_START_SECTION_END]); 2180 } 2181 switch (type) 2182 { 2183 case QITEM_BLOCK_START_TYPE: 2184 printQueueWord(start, QITEM_BLOCK_START_BLOCK_END, "block-end", out); 2185 printQueueStringWord(start, QITEM_BLOCK_START_PREFIX, "prefix", out); 2186 printQueueStringWord(start, QITEM_BLOCK_START_SUFFIX, "suffix", out); 2187 break; 2188 case QITEM_NEWLINE_TYPE: 2189 out.print('@'); 2190 out.print(start+QITEM_NEWLINE_KIND); 2191 out.print(": - kind: "); 2192 int kind = queueInts[start+QITEM_NEWLINE_KIND]; 2193 String skind = "???"; 2194 switch (kind) 2195 { 2196 case NEWLINE_LINEAR: skind = "linear"; break; 2197 case NEWLINE_LITERAL: skind = "literal"; break; 2198 case NEWLINE_DUMMY: skind = "dummy"; break; 2199 case NEWLINE_FILL: skind = "fill"; break; 2200 case NEWLINE_SPACE: skind = "space"; break; 2201 case NEWLINE_MISER: skind = "miser"; break; 2202 case NEWLINE_MANDATORY: skind = "mandatory"; break; 2203 } 2204 out.print(kind); 2205 out.print('('); 2206 out.print(skind); 2207 out.println(')'); 2208 break; 2209 case QITEM_INDENTATION_TYPE: 2210 printQueueWord(start, QITEM_INDENTATION_KIND, "kind", out); 2211 printQueueWord(start, QITEM_INDENTATION_AMOUNT, "amount", out); 2212 break; 2213 default: 2214 for (int i = 2; i < size; i++) 2215 printQueueWord(start, i, "word#"+i, out); 2216 } 2217 todo -= size; 2218 start += size; 2219 } 2220 int count = bufferFillPointer - bufIndex; 2221 if (count > 0) 2222 { 2223 out.print(count); out.print(" chars: \""); 2224 out.write(buffer, bufIndex, count); 2225 out.println('\"'); 2226 } 2227 } 2228 2229 private void printQueueWord(int start, int offset, 2230 String fname, PrintWriter out) 2231 { 2232 out.print('@'); 2233 out.print(start+offset); 2234 out.print(": - "); 2235 out.print(fname); 2236 out.print(": "); 2237 out.println(queueInts[start+offset]); 2238 } 2239 2240 private void printQueueStringWord(int start, int offset, 2241 String fname, PrintWriter out) 2242 { 2243 out.print('@'); 2244 out.print(start+offset); 2245 out.print(": - "); 2246 out.print(fname); 2247 out.print(": "); 2248 String str = queueStrings[start+offset]; 2249 if (str == null) 2250 out.println("null"); 2251 else 2252 { 2253 out.print('\"'); 2254 out.print(str); 2255 out.print('\"'); 2256 out.print(" length: "); 2257 out.println(str.length()); 2258 } 2259 } 2260 2261 void check (String where) 2262 { 2263 String msg = null; 2264 if (currentBlock != -1 2265 && ! (currentBlock < queueInts.length 2266 && currentBlock >= queueTail 2267 ? currentBlock < queueTail + queueSize 2268 : currentBlock < queueTail + queueSize - queueInts.length)) 2269 msg = ("currentBlock ("+currentBlock 2270 +") >= queue length ("+queueInts.length+")"); 2271 if (msg != null) 2272 { 2273 if (where != null) 2274 msg = "in "+where+": "+msg; 2275 log.println(msg); 2276 dumpQueue(); 2277 log.flush(); 2278 throw new Error(msg); 2279 } 2280 } 2281 2282 String itemKindString (int kind) 2283 { 2284 switch (kind) 2285 { 2286 case QITEM_NOP_TYPE: return "nop"; 2287 case QITEM_NEWLINE_TYPE: return "newline"; 2288 case QITEM_INDENTATION_TYPE: return "indentation"; 2289 case QITEM_BLOCK_START_TYPE: return "block-start"; 2290 case QITEM_BLOCK_END_TYPE: return "block-end"; 2291 case QITEM_TAB_TYPE: return "tab"; 2292 case QITEM_POSNMARKER_TYPE: return "posn-marker"; 2293 case QITEM_BACKREF_TYPE: return "backref"; 2294 case QITEM_PAIR_END_TYPE: return "pair-end"; 2295 case QITEM_EOE_TYPE: return "eoe"; 2296 default: return "("+kind+" - unknown)"; 2297 } 2298 } 2299 String enqueueExtraLog = ""; 2300 */ 2301 } 2302