1 // Copyright (c) 2001, 2002, 2006, 2016 Per M.A. Bothner. 2 // This is free software; for terms and warranty disclaimer see ./COPYING. 3 4 package gnu.kawa.functions; 5 import gnu.mapping.*; 6 import gnu.lists.*; 7 import gnu.math.RatNum; 8 import gnu.math.IntNum; 9 import gnu.math.UnsignedPrim; 10 import java.io.*; 11 import gnu.text.Char; 12 import gnu.expr.Keyword; 13 /* #ifdef enable:XML */ 14 import gnu.kawa.xml.KNode; 15 import gnu.xml.XMLPrinter; 16 /* #endif */ 17 import gnu.kawa.format.AbstractFormat; 18 import gnu.kawa.format.GenericFormat; 19 import gnu.kawa.format.GenericFormat.TryFormatResult; 20 import gnu.kawa.io.CheckConsole; 21 import gnu.kawa.io.OutPort; 22 import gnu.kawa.io.PrettyWriter; 23 import gnu.kawa.xml.XmlNamespace; 24 import gnu.kawa.lispexpr.LispLanguage; 25 import gnu.kawa.format.Printable; 26 /* #ifdef use:java.awt */ 27 import gnu.kawa.models.DrawImage; 28 import gnu.kawa.models.DrawShape; 29 import gnu.kawa.models.Picture; 30 import java.awt.Shape; 31 import java.awt.image.BufferedImage; 32 import gnu.kawa.models.SVGUtils; 33 /* #endif */ 34 import java.util.List; 35 /* #ifdef use:java.util.regex */ 36 import java.util.regex.*; 37 /* #endif */ 38 39 /** Handle formatted output for Lisp-like languages. */ 40 41 public class DisplayFormat extends GenericFormat 42 { 43 public static GenericFormat standardFormat = new GenericFormat(); 44 static { 45 Class thisCls = DisplayFormat.class; 46 // NOTE - order is important: The search order is from 47 // most-recently-added to older, so more specific types come later. 48 // Default - java.lang.Object or null standardFormat.add(thisCls, R)49 standardFormat.add(thisCls, "writeObjectDefault"); 50 // Printable or Consumable standardFormat.add(thisCls, R)51 standardFormat.add(thisCls, "writePrintableConsumable"); 52 // gnu.mapping.Values standardFormat.add(thisCls, R)53 standardFormat.add(thisCls, "writeValues"); 54 // gnu.mapping.Symbol standardFormat.add(thisCls, R)55 standardFormat.add(thisCls, "writeSymbol"); 56 // java.lang.Boolean standardFormat.add(thisCls, R)57 standardFormat.add(thisCls, "writeBoolean"); 58 // java.lang.CharValue or gnu.text.Char standardFormat.add(thisCls, R)59 standardFormat.add(thisCls, "writeChar"); 60 // gnu.mapping.Lazy standardFormat.add(thisCls, R)61 standardFormat.add(thisCls, "writePromise"); 62 // java.net.URI standardFormat.add(thisCls, R)63 standardFormat.add(thisCls, "writeURI"); 64 // gnu.lists.Array standardFormat.add(thisCls, R)65 standardFormat.add(thisCls, "writeArray"); 66 // java.util.List standardFormat.add(thisCls, R)67 standardFormat.add(thisCls, "writeSequence"); 68 // gnu.lists.LList standardFormat.add(thisCls, R)69 standardFormat.add(thisCls, "writeList"); 70 // gnu.lists.Range standardFormat.add(thisCls, R)71 standardFormat.add(thisCls, "writeRange"); 72 // java.lang.CharSequence standardFormat.add(thisCls, R)73 standardFormat.add(thisCls, "writeCharSeq"); 74 // gnu.kawa.xml.KNode 75 /* #ifdef enable:XML */ standardFormat.add(thisCls, R)76 standardFormat.add(thisCls, "writeKNode"); 77 /* #endif */ 78 // RatNum, UnsignedPrim, Long, Integer, Short, Byte, BigInteger standardFormat.add(thisCls, R)79 standardFormat.add(thisCls, "writeRational"); 80 // Picture, Shape, or BufferedImage 81 /* #ifdef use:java.awt */ standardFormat.add(thisCls, R)82 standardFormat.add(thisCls, "writePicture"); 83 /* #endif */ 84 // java.lang.Enum standardFormat.add(thisCls, R)85 standardFormat.add(thisCls, "writeEnum"); 86 // Java native arrays standardFormat.add(thisCls, R)87 standardFormat.add(thisCls, "writeJavaArray"); 88 } 89 90 /** Fluid parameter to specify default output base for printing rationals. */ 91 public static final ThreadLocation outBase 92 = new ThreadLocation("out-base"); IntNum.ten()93 static { outBase.setGlobal(IntNum.ten()); } 94 /** True if we should print a radix indicator when printing rationals. 95 * The default is no; otherwise we follow Common Lisp conventions. */ 96 public static final ThreadLocation outRadix 97 = new ThreadLocation("out-radix"); 98 99 public static final DisplayFormat schemeDisplayFormat 100 = new DisplayFormat(false, 'S'); 101 102 public static final DisplayFormat schemeWriteSimpleFormat 103 = new DisplayFormat(true, 'S'); 104 public static final DisplayFormat schemeWriteFormat 105 = new DisplayFormat(true, 'S'); 106 public static final DisplayFormat schemeWriteSharedFormat 107 = new DisplayFormat(true, 'S'); 108 static { 109 schemeWriteFormat.checkSharing = 0; 110 schemeWriteSharedFormat.checkSharing = 1; 111 } 112 113 /** Controls whether we check for sharing and cycles. 114 * 1: check for sharing; 0: check for cycles: -1: no checking. */ 115 public int checkSharing = -1; 116 117 /** Create a new instance. 118 * @param readable if output should be formatted so it could be read 119 * back in again, for example strings shoudl be quoted. 120 * @param language the programming language style to use, where 121 * 'S' is Scheme, 'C' is Common Lisp, and 'E' is Emacs Lisp. 122 */ DisplayFormat(boolean readable, char language)123 public DisplayFormat(boolean readable, char language) 124 { 125 super(standardFormat); 126 this.readable = readable; 127 this.language = language; 128 } 129 getEmacsLispFormat(boolean readable)130 public static DisplayFormat getEmacsLispFormat(boolean readable) 131 { 132 return new DisplayFormat(readable, 'E'); 133 } 134 getCommonLispFormat(boolean readable)135 public static DisplayFormat getCommonLispFormat(boolean readable) 136 { 137 return new DisplayFormat(readable, 'C'); 138 } 139 getSchemeFormat(boolean readable)140 public static DisplayFormat getSchemeFormat(boolean readable) 141 { 142 return new DisplayFormat(readable, 'S'); 143 } 144 145 boolean readable; 146 147 /** 'S' is Scheme-style; 'C' is CommonLisp-style; 'E' is Emacs-style. 148 * Note Emacs has its own sub-class gnu.jemacs.lang.Print. */ 149 char language; 150 151 @Override getReadableOutput()152 public boolean getReadableOutput() { return readable; } 153 154 @Override textIsCopied()155 public boolean textIsCopied() { return ! readable; } 156 157 @Override writeBoolean(boolean v, Consumer out)158 public void writeBoolean(boolean v, Consumer out) 159 { 160 write (language == 'S' ? (v ? "#t" : "#f") : (v ? "t" : "nil"), out); 161 } writeBoolean(Object v, AbstractFormat f, Consumer out)162 public static TryFormatResult writeBoolean(Object v, AbstractFormat f, Consumer out) { 163 if (! (v instanceof Boolean)) 164 return TryFormatResult.INVALID_CLASS; 165 f.writeBoolean(((Boolean) v).booleanValue(), out); 166 return TryFormatResult.HANDLED; 167 } 168 writeChar(Object v, AbstractFormat f, Consumer out)169 public static TryFormatResult writeChar(Object v, AbstractFormat f, Consumer out) { 170 boolean readable; 171 char language; 172 if (f instanceof DisplayFormat) { 173 DisplayFormat dformat = (DisplayFormat) f; 174 readable = dformat.readable; 175 language = dformat.language; 176 } else { 177 readable = false; 178 language = 'S'; 179 } 180 if (v instanceof Char) { 181 writeChar(((Char) v).intValue(), readable, language, out); 182 return TryFormatResult.HANDLED; 183 } else if (v instanceof Character) { 184 writeChar(((Character) v).charValue(), readable, language, out); 185 return TryFormatResult.HANDLED; 186 } 187 return TryFormatResult.INVALID_CLASS; 188 } 189 writeRational(Object obj, AbstractFormat format, Consumer out)190 public static TryFormatResult writeRational(Object obj, AbstractFormat format, 191 Consumer out) { 192 if (obj instanceof RatNum 193 || obj instanceof UnsignedPrim 194 || (obj instanceof Number 195 && (obj instanceof Long 196 || obj instanceof Integer 197 || obj instanceof Short 198 || obj instanceof Byte 199 || obj instanceof java.math.BigInteger))) { 200 int b = 10; 201 boolean showRadix = false; 202 Object base = outBase.get(null); 203 Object printRadix = outRadix.get(null); 204 if (printRadix != null 205 && (printRadix == Boolean.TRUE 206 || "yes".equals(printRadix.toString()))) 207 showRadix = true; 208 if (base instanceof Number) 209 b = ((IntNum) base).intValue(); 210 else if (base != null) 211 b = Integer.parseInt(base.toString()); 212 String asString = Arithmetic.asRatNum(obj).toString(b); 213 if (showRadix) { 214 if (b == 16) 215 out.write("#x"); 216 else if (b == 8) 217 out.write("#o"); 218 else if (b == 2) 219 out.write("#b"); 220 else if (b != 10 || ! (obj instanceof IntNum)) 221 out.write("#"+base+"r"); 222 } 223 out.write(asString); 224 if (showRadix && b == 10 && obj instanceof IntNum) 225 out.write("."); 226 return TryFormatResult.HANDLED; 227 } 228 return TryFormatResult.INVALID_CLASS; 229 } 230 writeChar(int v, boolean readable, char language, Consumer out)231 public static void writeChar(int v, boolean readable, char language, Consumer out) 232 { 233 if (! readable) 234 Char.print(v, out); 235 else 236 { 237 if (language == 'E' 238 && v > ' ') 239 { 240 out.write('?'); 241 Char.print(v, out); 242 } 243 // else if (language == 'E') ... 244 else 245 out.write(Char.toScmReadableString(v)); 246 } 247 } 248 249 /** 250 * Format a list. 251 * 252 * Try to find shared structures in a list. To accomplish this, each subobject 253 * is hashed to the idhash, which is used later to determine whether we've seen 254 * a subobject before. There is an added complication when you consider cases 255 * like this: 256 * 257 * '((b . #1=(a . z)) 3) 258 * 259 * It is not known in advance that the printer will have to emit an extra 260 * ')' after the '(a . z) pair. Every time we CDR further into the list, we 261 * push a position marker onto a stack. Once we've examined the tail of this 262 * sublist, we pop all the posn markers off and tell the pretty printer that 263 * it might have to emit an extra ')' if the corresponding posn marker 264 * becomes active. 265 * 266 * @param list The list on which the method CDR's, termination occurs when 267 * this becomes a non-pair or the empty list 268 * @param out The output port that is responsible for the pretty printing 269 */ writeList(Object list, AbstractFormat format, Consumer out)270 public static TryFormatResult writeList(Object list, AbstractFormat format, Consumer out) 271 { 272 if (! (list instanceof LList)) 273 return TryFormatResult.INVALID_CLASS; 274 PrettyWriter pout = 275 out instanceof PrintConsumer ? ((PrintConsumer) out).getPrettyWriter() 276 : null; 277 boolean readable = format instanceof DisplayFormat ? ((DisplayFormat) format).readable : false; 278 // The stack of position markers, populated by CDR'ing further into the list. 279 int[] posnStack = null; 280 int checkSharing = format instanceof DisplayFormat ? ((DisplayFormat) format).checkSharing : -1; 281 Object[] tailStack = null; 282 int stackTail = 0; 283 PrintConsumer.startLogicalBlock("(", false, ")", out); 284 285 while (list instanceof Pair) 286 { 287 Pair pair = (Pair) list; 288 format.writeObject(pair.getCar(), out); 289 list = pair.getCdr(); 290 if (! readable) 291 list = Promise.force(list); 292 if (list == LList.Empty) 293 break; 294 PrintConsumer.writeSpaceFill(out); 295 if (! (list instanceof Pair)) 296 { 297 out.write(". "); 298 format.writeObject(LList.checkNonList(list), (Consumer) out); 299 break; 300 } 301 if (pout != null && checkSharing >= 0) 302 { 303 304 int hashIndex = pout.IDHashLookup(list); 305 int posn = pout.IDHashGetFromIndex(hashIndex); 306 if (posn == -1) 307 // Then this is a new (sub)object to be marked 308 { 309 // writePositionMarker will return the index to which is was enqueued 310 posn = pout.writePositionMarker(true); 311 if (posnStack == null) { 312 posnStack = new int[128]; // should be plently for most cases. 313 tailStack = new Object[128]; 314 } 315 else if (stackTail >= posnStack.length) 316 { 317 int[] newPStack = new int[posnStack.length << 1]; 318 System.arraycopy(posnStack, 0, newPStack, 0, stackTail); 319 posnStack = newPStack; 320 Object[] newTStack = new Object[posnStack.length << 1]; 321 System.arraycopy(tailStack, 0, newTStack, 0, stackTail); 322 tailStack = newTStack; 323 } 324 posnStack[stackTail] = posn; 325 tailStack[stackTail++] = list; 326 // Mark (hash) this object 327 pout.IDHashPutAtIndex(list, posn, hashIndex); 328 } 329 else 330 { 331 out.write(". "); 332 pout.writeBreak(PrettyWriter.NEWLINE_FILL); 333 pout.writeBackReference(posn); 334 list = LList.Empty; 335 break; 336 } 337 } 338 } 339 for (;--stackTail >= 0;) { 340 pout.writePairEnd(posnStack[stackTail]); 341 if (checkSharing == 0) 342 pout.IDHashRemove(tailStack[stackTail]); 343 } 344 345 PrintConsumer.endLogicalBlock(")", out); 346 return TryFormatResult.HANDLED; 347 } 348 writeArray(Object value, AbstractFormat format, Consumer out)349 public static TryFormatResult writeArray(Object value, AbstractFormat format, 350 Consumer out) { 351 if (! (value instanceof Array)) 352 return TryFormatResult.INVALID_CLASS; 353 if (! format.getReadableOutput() 354 && out instanceof OutPort // FIXME PrintConsumer? 355 && ((OutPort) out).atLineStart() 356 && ((OutPort) out).isPrettyPrinting()) 357 out.write(ArrayPrint.print(value, null)); 358 else 359 write((Array) value, 0, 0, format, out); 360 return TryFormatResult.HANDLED; 361 } 362 writeRange(Object value, AbstractFormat format, Consumer out)363 public static TryFormatResult writeRange(Object value, AbstractFormat format, 364 Consumer out) { 365 if (! (value instanceof Range)) 366 return TryFormatResult.INVALID_CLASS; 367 Range range = (Range) value; 368 if (! (format.getReadableOutput() || range.isUnspecifiedStart())) 369 return writeSequence(range, format, out); 370 PrintConsumer.startLogicalBlock("[", false, "]", out); 371 Object rstart = range.getStart(); 372 Object rstep = range.getStep(); 373 IntNum istep = IntNum.asIntNumOrNull(rstep); 374 IntNum istart = IntNum.asIntNumOrNull(rstart); 375 if (range.isUnspecifiedStart()) { 376 if (istep.isOne()) 377 out.write("<:"); 378 else if (istep.isMinusOne()) 379 out.write(">:"); 380 else { 381 out.write("by: "); 382 format.writeObject(rstep, out); 383 } 384 } else { 385 format.writeObject(rstart, out); 386 int rsize = range.size(); 387 if (! range.isUnbounded() && istart != null && istep != null 388 && (istep.isOne() || istep.isMinusOne())) { 389 if (istep.isOne()) { 390 out.write(" <: "); 391 format.writeObject(IntNum.add(istart, rsize), out); 392 } else { 393 out.write(" >: "); 394 format.writeObject(IntNum.add(istart, -rsize), out); 395 } 396 } else { 397 out.write(" by: "); 398 format.writeObject(rstep, out); 399 if (! range.isUnbounded()) { 400 out.write(" size: "); 401 out.writeInt(rsize); 402 } 403 } 404 } 405 PrintConsumer.endLogicalBlock("]", out); 406 return TryFormatResult.HANDLED; 407 } 408 writeJavaArray(Object value, AbstractFormat format, Consumer out)409 public static TryFormatResult writeJavaArray(Object value, AbstractFormat format, 410 Consumer out) { 411 if (value == null || ! value.getClass().isArray()) 412 return TryFormatResult.INVALID_CLASS; 413 int len = java.lang.reflect.Array.getLength(value); 414 PrintConsumer.startLogicalBlock("[", false, "]", out); 415 for (int i = 0; i < len; i++) { 416 if (i > 0) 417 PrintConsumer.writeSpaceFill(out); 418 format.writeObject(java.lang.reflect.Array.get(value, i), out); 419 } 420 PrintConsumer.endLogicalBlock("]", out); 421 return TryFormatResult.HANDLED; 422 } 423 writeSequence(Object value, AbstractFormat format, Consumer out)424 public static TryFormatResult writeSequence(Object value, AbstractFormat format, 425 Consumer out) { 426 if (! (value instanceof List)) 427 return TryFormatResult.INVALID_CLASS; 428 List vec = (List) value; 429 String tag = 430 vec instanceof SimpleVector ? ((SimpleVector) vec).getTag() : null; 431 String start, end; 432 if (format instanceof DisplayFormat 433 && ((DisplayFormat) format).language == 'E') 434 { 435 start = "["; 436 end = "]"; 437 } 438 else if ("b".equals(tag)) 439 { 440 start = "#*"; 441 end = ""; 442 } 443 else 444 { 445 start = tag == null ? "#(" : ("#" + tag + "("); 446 end = ")"; 447 } 448 PrintConsumer.startLogicalBlock(start, false, end, out); 449 // Using consumeNext for primtives avoids boxing. 450 // However, for objects we want to recurse 451 if ("b".equals(tag)) { 452 SimpleVector bvec = (SimpleVector) vec; 453 int blen = vec.size(); 454 for (int i = 0; i < blen; i++) { 455 boolean b = bvec.getBooleanRaw(bvec.effectiveIndex(i)); 456 out.write(b ? '1' : '0'); 457 } 458 } else if (vec instanceof SimpleVector && tag != null) { 459 int endpos = vec.size() << 1; 460 for (int ipos = 0; ipos < endpos; ipos += 2) { 461 if (ipos > 0) 462 PrintConsumer.writeSpaceFill(out); 463 if (! ((SimpleVector) vec).consumeNext(ipos, out)) 464 break; 465 } 466 } else { 467 boolean first = true; 468 for (Object el : vec) { 469 if (first) 470 first = false; 471 else 472 PrintConsumer.writeSpaceFill(out); 473 format.writeObject(el, out); 474 } 475 } 476 PrintConsumer.endLogicalBlock(end, out); 477 return TryFormatResult.HANDLED; 478 } 479 writeValues(Object value, AbstractFormat format, Consumer out)480 public static TryFormatResult writeValues(Object value, AbstractFormat format, 481 Consumer out) { 482 if (! (value instanceof Values)) 483 return TryFormatResult.INVALID_CLASS; 484 if (value == Values.empty && format.getReadableOutput()) 485 out.write("#!void"); 486 else { 487 Values values = (Values) value; 488 for (int it = 0; (it = values.nextPos(it)) != 0; ) { 489 Object val = values.getPosPrevious(it); 490 format.writeObject(val, out); 491 } 492 } 493 return TryFormatResult.HANDLED; 494 } 495 writePrintableConsumable(Object value, AbstractFormat format, Consumer out)496 public static TryFormatResult writePrintableConsumable 497 (Object value, AbstractFormat format, Consumer out) { 498 if (value instanceof Consumable 499 && (! format.getReadableOutput() 500 || ! (value instanceof Printable))) 501 ((Consumable) value).consume(out); 502 else if (value instanceof Printable) 503 ((Printable) value).print(out); 504 else 505 return TryFormatResult.INVALID_CLASS; 506 return TryFormatResult.HANDLED; 507 } 508 writeObject(Object obj, Consumer out)509 public void writeObject(Object obj, Consumer out) { 510 PrettyWriter pout = out instanceof PrintConsumer 511 ? ((PrintConsumer) out).getPrettyWriter() 512 : null; 513 boolean popIDHashNeeded = false; 514 boolean space = false; 515 boolean skip = false; 516 if (out instanceof PrintConsumer 517 && ! (obj instanceof gnu.kawa.xml.UntypedAtomic) 518 && ! (obj instanceof Values) 519 && (getReadableOutput() 520 || ! (obj instanceof Char || obj instanceof Character 521 || obj instanceof CharSequence))) { 522 ((PrintConsumer) out).writeWordStart(); 523 space = true; 524 } 525 boolean removeNeeded = false; 526 try { 527 if (pout != null && checkSharing >= 0 && isInteresting(obj)) { 528 popIDHashNeeded = pout.initialiseIDHash(); 529 pout.setSharing(true); 530 // The value returned from this hash is the respective index in the 531 // queueInts[] from PrettyWriter to which this object should reference. 532 int hashIndex = pout.IDHashLookup(obj); 533 int posn = pout.IDHashGetFromIndex(hashIndex); 534 if (posn == -1) { 535 // Find the position in the queueInts that 536 // future (if any) backreferences should reference 537 int nposn = pout.writePositionMarker(false); 538 // Mark (hash) this object 539 pout.IDHashPutAtIndex(obj, nposn, hashIndex); 540 removeNeeded = checkSharing == 0; 541 // Print the object, instead of emitting print-circle notation 542 skip = false; 543 } else { 544 // This object is referring to another part of the expression. 545 // Activate the referenced position marker 546 pout.writeBackReference(posn); 547 // This object is referring to the structure, we shall not 548 // print it and instead we shall emit print-circle notation. 549 skip = true; 550 // Format a fill space after the #N# token 551 space = true; 552 } 553 } 554 if (!skip) 555 super.writeObject(obj, out); 556 } finally { 557 if (removeNeeded) 558 pout.IDHashRemove(obj); 559 if (space) 560 ((PrintConsumer) out).writeWordEnd(); 561 if (popIDHashNeeded) { 562 pout.setSharing(true); 563 pout.finishIDHash(); 564 } 565 } 566 } 567 writeObjectDefault(Object obj, AbstractFormat format, Consumer out)568 public static TryFormatResult writeObjectDefault(Object obj, AbstractFormat format, 569 Consumer out) 570 { 571 boolean readable = format.getReadableOutput(); 572 char language = 573 format instanceof DisplayFormat ? ((DisplayFormat) format).language 574 : 'S'; 575 if (obj instanceof Consumable 576 && (! readable || ! (obj instanceof Printable))) 577 ((Consumable) obj).consume(out); 578 else if (obj instanceof Printable) 579 ((Printable) obj).print(out); 580 else 581 { 582 String asString = obj == null ? null : obj.toString(); 583 out.write(asString == null ? "#!null" : asString); 584 } 585 return TryFormatResult.HANDLED; 586 } 587 588 /** Recursive helper method for writing out Array (sub-) objects. 589 * @param array the Array to write out (part of). 590 * @param index the row-major index to start 591 * @param level the recurssion level, from 0 to array.rank()-1. 592 * @param out the destination 593 */ write(Array array, int index, int level, AbstractFormat format, Consumer out)594 static int write(Array array, int index, int level, AbstractFormat format, Consumer out) 595 { 596 int rank = array.rank(); 597 int count = 0; 598 String start; 599 if (level > 0) 600 start = "("; 601 else { 602 boolean printDims = false; 603 int i = rank; 604 while (--i >= 0) { 605 if (array.getLowBound(i) != 0 || array.getSize(i) == 0) 606 break; 607 } 608 StringBuilder sbuf = new StringBuilder(); 609 sbuf.append('#'); 610 sbuf.append(rank); 611 String tag = array instanceof GeneralArray 612 ? ((GeneralArray) array).getTag() 613 : null; 614 sbuf.append(tag == null ? 'a' : tag); 615 if (i >= 0) { 616 for (i = 0; i < rank; i++) { 617 int low = array.getLowBound(i); 618 if (low != 0) { 619 sbuf.append('@'); 620 sbuf.append(low); 621 } 622 sbuf.append(':'); 623 sbuf.append(array.getSize(i)); 624 } 625 } 626 sbuf.append(rank == 0 ? ' ' : '('); 627 start = sbuf.toString(); 628 } 629 String end = rank == 0 ? "" : ")"; 630 PrintConsumer.startLogicalBlock(start, false, end, out); 631 if (rank > 0) 632 { 633 int size = array.getSize(level); 634 level++; 635 for (int i = 0; i < size; i++) 636 { 637 if (i > 0) 638 PrintConsumer.writeSpaceFill(out); 639 int step; 640 if (level == rank) 641 { 642 format.writeObject(array.getRowMajor(index), out); 643 step = 1; 644 } 645 else 646 step = write(array, index, level, format, out); 647 index += step; 648 count += step; 649 } 650 } 651 else 652 format.writeObject(array.getRowMajor(index), out); 653 PrintConsumer.endLogicalBlock(end, out); 654 return count; 655 } 656 657 /* #ifdef use:java.util.regex */ 658 static Pattern r5rsIdentifierMinusInteriorColons = 659 Pattern.compile("(([a-zA-Z]|[!$%&*/:<=>?^_~])" 660 + "([a-zA-Z]|[!$%&*/<=>?^_~]|[0-9]|([-+.@]))*[:]?)" 661 + "|([-+]|[.][.][.])"); 662 /* #endif */ 663 writeCharSeq(Object value, AbstractFormat format, Consumer out)664 public static TryFormatResult writeCharSeq(Object value, AbstractFormat format, Consumer out) { 665 if (! (value instanceof CharSequence)) 666 return TryFormatResult.INVALID_CLASS; 667 CharSequence str = (CharSequence) value; 668 if (format.getReadableOutput()) 669 Strings.printQuoted(str, out, 1); 670 else if (value instanceof String) 671 { 672 out.write(value.toString()); 673 } 674 else if (value instanceof FString || value instanceof IString) 675 { // For some other Consumable types (such as Blob) consume does the wrong thing 676 ((Consumable) value).consume(out); 677 } 678 else 679 { 680 int len = str.length(); 681 for (int i = 0; i < len; i++) 682 out.write(str.charAt(i)); 683 } 684 return TryFormatResult.HANDLED; 685 } 686 writeEnum(Object value, AbstractFormat format, Consumer out)687 public static TryFormatResult writeEnum(Object value, AbstractFormat format, Consumer out) { 688 if (! (value instanceof java.lang.Enum)) 689 return TryFormatResult.INVALID_CLASS; 690 if (! format.getReadableOutput()) 691 return TryFormatResult.INVALID; 692 out.write(value.getClass().getName()); 693 out.write(":"); 694 out.write(((java.lang.Enum) value).name()); 695 return TryFormatResult.HANDLED; 696 } 697 writeSymbol(Object value, AbstractFormat format, Consumer out)698 public static TryFormatResult writeSymbol(Object value, AbstractFormat format, Consumer out) { 699 if (! (value instanceof Symbol)) 700 return TryFormatResult.INVALID_CLASS; 701 Symbol sym = (Symbol) value; 702 Namespace ns = sym.getNamespace(); 703 if (ns == XmlNamespace.HTML) { 704 out.write("html:"); 705 out.write(sym.getLocalPart()); 706 } else if (ns == LispLanguage.entityNamespace 707 || ns == LispLanguage.constructNamespace) { 708 out.write(ns.getPrefix()); 709 out.write(":"); 710 out.write(sym.getLocalPart()); 711 } else { 712 String prefix = sym.getPrefix(); 713 Namespace namespace = sym.getNamespace(); 714 String uri = namespace == null ? null : namespace.getName(); 715 boolean readable = format.getReadableOutput(); 716 boolean hasUri = uri != null && uri.length() > 0; 717 boolean hasPrefix = prefix != null && prefix.length() > 0; 718 boolean suffixColon = false; 719 if (namespace == Keyword.keywordNamespace) { 720 char language = format instanceof DisplayFormat ? ((DisplayFormat) format).language : 'S'; 721 if (language == 'C' || language == 'E') 722 out.write(':'); 723 else 724 suffixColon = true; 725 } else if (hasPrefix || hasUri) { 726 if (hasPrefix) 727 writeSymbol(prefix, out, readable); 728 if (hasUri && (readable || ! hasPrefix)) 729 { 730 out.write('{'); 731 out.write(uri); 732 out.write('}'); 733 } 734 out.write(':'); 735 } 736 writeSymbol(sym.getName(), out, readable); 737 if (suffixColon) 738 out.write(':'); 739 } 740 return TryFormatResult.HANDLED; 741 } 742 writeSymbol(String sym, Consumer out, boolean readable)743 static void writeSymbol (String sym, Consumer out, boolean readable) { 744 /* #ifdef use:java.util.regex */ 745 /* Use |...| if symbol doesn't follow R5RS conventions 746 for identifiers or has a colon in the interior. */ 747 if (readable 748 && ! r5rsIdentifierMinusInteriorColons.matcher(sym).matches()) { 749 int len = sym.length(); 750 boolean r7rsStyle = true; // FIXME 751 if (r7rsStyle) { 752 out.write('|'); 753 for (int i = 0; i < len; i++) { 754 int ch = sym.charAt(i); 755 if (ch >= 0xD800 && ch <= 0xDBFF) { // surrogates 756 char next = sym.charAt(++i); 757 if (next >= 0xDC00 && next <= 0xDFFF) 758 ch = ((ch - 0xD800) << 10) 759 + (next - 0xDC00) + 0x10000; 760 } 761 if (ch == '|' || ch == '\\' || ch < ' ' || ch == 127) { 762 out.write('\\'); 763 switch(ch) { 764 case '\007': out.write('a'); break; 765 case '\b': out.write('b'); break; 766 case '\n': out.write('n'); break; 767 case '\r': out.write('r'); break; 768 case '\t': out.write('t'); break; 769 case '\\': out.write('\\'); break; 770 case '|': out.write('|'); break; 771 default: 772 out.write('x'); 773 writeHexDigits(ch, out); 774 out.write(';'); 775 } 776 } 777 else 778 Char.print(ch, out); 779 } 780 out.write('|'); 781 } else if (len == 0) { 782 out.write("||"); 783 } else { 784 boolean inVerticalBars = false; 785 for (int i = 0; i < len; i++) { 786 char ch = sym.charAt(i); 787 if (ch == '|') { 788 out.write(inVerticalBars ? "|\\" : "\\"); 789 inVerticalBars = false; 790 } else if (! inVerticalBars) { 791 out.write('|'); 792 inVerticalBars = true; 793 } 794 out.write(ch); 795 } 796 if (inVerticalBars) 797 out.write('|'); 798 } 799 return; 800 } 801 /* #endif */ 802 out.write(sym); 803 } 804 805 /* #ifdef use:java.awt */ writePicture(Object value, AbstractFormat format, Consumer out)806 public static TryFormatResult writePicture(Object value, 807 AbstractFormat format, Consumer out) { 808 Picture pic = DrawImage.toPictureOrNull(value); 809 if (pic == null) 810 return TryFormatResult.INVALID_CLASS; 811 if (format.getReadableOutput()) 812 return TryFormatResult.INVALID; 813 if (! CheckConsole.forDomTerm(out)) 814 return writeObjectDefault(value, format, out); 815 out.write("\033]72;"+SVGUtils.toSVG(pic)+"\007"); 816 return TryFormatResult.HANDLED; 817 } 818 /* #endif */ 819 820 /* #ifdef enable:XML */ writeKNode(Object value, AbstractFormat format, Consumer out)821 public static TryFormatResult writeKNode(Object value, 822 AbstractFormat format, Consumer out) { 823 if (value instanceof KNode) { 824 boolean escapeForDomTerm = false; 825 if (format.getReadableOutput()) 826 out.write("#"); 827 else if (CheckConsole.forDomTerm(out)) { 828 out.write("\033]72;"); 829 escapeForDomTerm = true; 830 } 831 XMLPrinter.make(out, "xhtml").writeObject(value); 832 if (escapeForDomTerm) 833 out.write("\007"); 834 return TryFormatResult.HANDLED; 835 } 836 return TryFormatResult.INVALID_CLASS; 837 } 838 /* #endif */ 839 writePromise(Object value, AbstractFormat format, Consumer out)840 public static TryFormatResult writePromise(Object value, 841 AbstractFormat format, Consumer out) { 842 if (! (value instanceof Lazy)) 843 return TryFormatResult.INVALID_CLASS; 844 if (format.getReadableOutput()) 845 return TryFormatResult.INVALID; 846 format.writeObject(((Lazy) value).getValue(), out); 847 return TryFormatResult.HANDLED; 848 } writeURI(Object value, AbstractFormat format, Consumer out)849 public static TryFormatResult writeURI(Object value, 850 AbstractFormat format, Consumer out) { 851 if (! (value instanceof java.net.URI)) 852 return TryFormatResult.INVALID_CLASS; 853 if (! format.getReadableOutput()) 854 return TryFormatResult.INVALID; 855 out.write("#,(URI "); 856 Strings.printQuoted(value.toString(), out, 1); 857 out.write(')'); 858 return TryFormatResult.HANDLED; 859 } 860 writeHexDigits(int i, Consumer out)861 static void writeHexDigits(int i, Consumer out) { 862 int high = i >>> 4; 863 if (high != 0) { 864 writeHexDigits(high, out); 865 i &= 15; 866 } 867 out.write("0123456789ABCDEF".charAt(i)); 868 } 869 870 /** 871 * An "interesting" object is one where object identity is significant. 872 * 873 * Examples are vectors and lists. 874 * No immutable values (for example symbols or numbers) are interesting 875 * @param obj The object under test 876 * @return true if this object is considered interesting 877 */ isInteresting(Object obj)878 private boolean isInteresting (Object obj) 879 { 880 // FIXME Should probably consider empty lists and vectors, as well as FStrings 881 return obj instanceof Pair || obj instanceof SimpleVector; 882 } 883 884 } 885