1/* 2 * Copyright (c) 2021-2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16package std.core; 17 18/** 19 * The maximum depth of printing nested objects 20 * Useful for cyclic linked objects 21 */ 22const MAX_CONSOLE_PRINT_DEPTH: int = 10 23 24enum LogLevel { 25 DEBUG = 0, 26 INFO = 1, 27 LOG = 2, 28 WARN = 3, 29 ERROR = 4, 30 PRINTLN = 5, 31} 32 33/** 34 * A Console class that provides access to standard output and error streams. 35 * Supports printing various data types, timing operations, and indentation management. 36 * @export 37 * @final 38 */ 39export final class Console { 40 41 /** 42 * @section Internal fields 43 */ 44 private lvl2Buf: Map<LogLevel, StringBuilder> 45 46 /** 47 * Map to store named timers for performance measurements 48 * @private 49 */ 50 private timers: Map<string, long> 51 52 /** 53 * Current indentation level for formatted output 54 * @private 55 */ 56 private indentationLevel: int = 0; 57 58 /** 59 * Number of spaces used for each level of indentation 60 * @private 61 * @static 62 * @readonly 63 */ 64 private static readonly INDENT_SIZE: int = 2; 65 66 /** 67 * Represents the space creating the margin for indentation 68 * @private 69 * @static 70 * @readonly 71 */ 72 private static readonly INDENT_MARGIN: string = " ".repeat(Console.INDENT_SIZE) 73 74 /** 75 * Map to store named counters for counting operations 76 * @private 77 */ 78 private counters: Map<string, int> 79 80 /** 81 * Internal constructor for Console class 82 * @internal 83 */ 84 internal constructor() { 85 this.timers = new Map<string, long> 86 this.counters = new Map<string, int> 87 this.lvl2Buf = new Map<LogLevel, StringBuilder> 88 this.lvl2Buf.set(LogLevel.DEBUG, new StringBuilder) 89 this.lvl2Buf.set(LogLevel.INFO, new StringBuilder) 90 this.lvl2Buf.set(LogLevel.LOG, new StringBuilder) 91 this.lvl2Buf.set(LogLevel.WARN, new StringBuilder) 92 this.lvl2Buf.set(LogLevel.ERROR, new StringBuilder) 93 this.lvl2Buf.set(LogLevel.PRINTLN, new StringBuilder) 94 } 95 96 private native printString(s: String, lvl: int): void; 97 98 private addToBuffer(s: String, level: LogLevel): void { 99 if (s.length == 0) { 100 return 101 } 102 let buf = this.lvl2Buf.get(level)! 103 buf.append(s) 104 // buffering strategies can be applied here based on buf size 105 this.printString(buf.toString(), level.valueOf()) 106 this.lvl2Buf.set(level, new StringBuilder) 107 } 108 109 private get indent(): String { 110 return Console.INDENT_MARGIN.repeat(this.indentationLevel) 111 } 112 113 private static NullishTypeToPrint(o: NullishType): String { 114 return Value.of(o).toPrint(MAX_CONSOLE_PRINT_DEPTH) 115 } 116 117 /** 118 * Implementations for printing primitive types 119 * @param {string | boolean | byte | short | char | int | long | float | double | NullishType} i - The value to print 120 * @returns {void} 121 * @public 122 * @deprecated Will be removed in future. Please use log instead 123 */ 124 public print(i: String): void { 125 this.addToBuffer(i, LogLevel.PRINTLN) 126 }; 127 public print(i: boolean): void { 128 this.addToBuffer(new Boolean(i).toString(), LogLevel.PRINTLN) 129 }; 130 public print(i: byte): void { 131 this.addToBuffer(new Byte(i).toString(), LogLevel.PRINTLN) 132 }; 133 public print(i: short): void { 134 this.addToBuffer(new Short(i).toString(), LogLevel.PRINTLN) 135 }; 136 public print(i: char): void { 137 this.addToBuffer(new Char(i).toString(), LogLevel.PRINTLN) 138 }; 139 public print(i: int): void { 140 this.addToBuffer(new Int(i).toString(), LogLevel.PRINTLN) 141 }; 142 public print(i: long): void { 143 this.addToBuffer(new Long(i).toString(), LogLevel.PRINTLN) 144 }; 145 public print(i: float): void { 146 this.addToBuffer(new Float(i).toString(), LogLevel.PRINTLN) 147 } 148 public print(i: double): void { 149 this.addToBuffer(new Double(i).toString(), LogLevel.PRINTLN) 150 } 151 public print(o: NullishType): void { 152 this.print(Console.NullishTypeToPrint(o)) 153 } 154 155 /** 156 * @section Println definitions 157 */ 158 159 /** 160 * Prints a newline 161 * @returns {void} 162 * @public 163 * @deprecated Will be removed in future. Please use log instead 164 */ 165 public println(): void { 166 this.print(c'\n') 167 } 168 169 /** 170 * Prints an object followed by a newline to stdout 171 * @param {NullishType | string | boolean | byte | short | char | int | long | float | double} i - The value to print 172 * @returns {void} 173 * @public 174 * @deprecated Will be removed in future. Please use log instead 175 */ 176 public println(i: NullishType): void { 177 this.print(i) 178 this.println() 179 } 180 public println(i: String): void { 181 this.print(i) 182 this.println() 183 } 184 public println(i: boolean): void { 185 this.print(i) 186 this.println() 187 } 188 public println(i: byte): void { 189 this.print(i) 190 this.println() 191 } 192 public println(i: short): void { 193 this.print(i) 194 this.println() 195 } 196 public println(i: char): void { 197 this.print(i) 198 this.println() 199 } 200 public println(i: int): void { 201 this.print(i) 202 this.println() 203 } 204 public println(i: long): void { 205 this.print(i) 206 this.println() 207 } 208 public println(i: float): void { 209 this.print(i) 210 this.println() 211 } 212 public println(i: double): void { 213 this.print(i) 214 this.println() 215 } 216 217 /** 218 * @section Console logging API 219 */ 220 221 /** 222 * Implementations for log primitive types to stdout 223 * @param {string | boolean | byte | short | char | int | long | NullishType} i - The value to print to stdout 224 * @returns {void} 225 * @public 226 */ 227 // NOTE(ivan-tyulyandin): overload are added for performance reasons 228 // To optimize `debug`, `warn`, `info` and `error` the same technique can be applied 229 public log(i: String): void { 230 this.addToBuffer(this.indent + i + c'\n', LogLevel.LOG) 231 } 232 public log(i: boolean): void { 233 this.addToBuffer(this.indent + i + c'\n', LogLevel.LOG) 234 } 235 public log(i: byte): void { 236 this.addToBuffer(this.indent + i + c'\n', LogLevel.LOG) 237 } 238 public log(i: short): void { 239 this.addToBuffer(this.indent + i + c'\n', LogLevel.LOG) 240 } 241 public log(i: char): void { 242 this.addToBuffer(this.indent + i + c'\n', LogLevel.LOG) 243 } 244 public log(i: int): void { 245 this.addToBuffer(this.indent + i + c'\n', LogLevel.LOG) 246 } 247 public log(i: long): void { 248 this.addToBuffer(this.indent + i + c'\n', LogLevel.LOG) 249 } 250 public log(i: float): void { 251 this.addToBuffer(this.indent + i + c'\n', LogLevel.LOG) 252 } 253 public log(i: double): void { 254 this.addToBuffer(this.indent + i + c'\n', LogLevel.LOG) 255 } 256 public log(): void { 257 this.addToBuffer("\n", LogLevel.LOG) 258 } 259 260 /** 261 * @section Variadics 262 */ 263 264 private printRest(level: LogLevel, ...vals: NullishType[]) { 265 let sb = new StringBuilder() 266 267 if (vals.length != 0) { 268 sb.append(this.indent) 269 const dFst = vals[0] 270 const hasFmt = (dFst instanceof String) && (dFst as String).indexOf(c'%') != -1 271 if (hasFmt) { 272 vals.shift() 273 sb.append((new Formatter).format(dFst as String, vals)) 274 } else { 275 sb.append(Console.NullishTypeToPrint(dFst)) 276 for (let i = 1; i < vals.length; ++i) { 277 sb.append(" " + Console.NullishTypeToPrint(vals[i])) 278 } 279 } 280 } 281 282 sb.append(c'\n') 283 this.addToBuffer(sb.toString(), level) 284 } 285 286 /** 287 * Prints log-level messages 288 * If first argument is a string it is treated as a format string 289 * @param {...NullishType[]} vals - Variable number of values to be logged 290 * @returns {void} 291 * @public 292 */ 293 public log(...vals: NullishType[]): void { 294 this.printRest(LogLevel.LOG, ...vals) 295 } 296 297 /** 298 * Prints debug-level messages 299 * If first argument is a string it is treated as a format string 300 * @param {...NullishType[]} vals - Variable number of values to be logged 301 * @returns {void} 302 * @public 303 */ 304 public debug(...vals: NullishType[]): void { 305 this.printRest(LogLevel.DEBUG, ...vals) 306 } 307 308 /** 309 * Prints info-level messages 310 * If first argument is a string it is treated as a format string 311 * @param {...NullishType[]} vals - Variable number of values to be logged 312 * @returns {void} 313 * @public 314 */ 315 public info(...vals: NullishType[]): void { 316 this.printRest(LogLevel.INFO, ...vals) 317 } 318 319 /** 320 * Prints warn-level messages 321 * If first argument is a string it is treated as a format string 322 * @param {...NullishType[]} vals - Variable number of values to be logged 323 * @returns {void} 324 * @public 325 */ 326 public warn(...vals: NullishType[]): void { 327 this.printRest(LogLevel.WARN, ...vals) 328 } 329 330 /** 331 * Prints error-level messages 332 * If first argument is a string it is treated as a format string 333 * @param {...NullishType[]} vals - Variable number of values to be logged 334 * @returns {void} 335 * @public 336 */ 337 public error(...vals: NullishType[]): void { 338 this.printRest(LogLevel.ERROR, ...vals) 339 } 340 341 /** 342 * Conditionally prints an error message if the assertion condition is false 343 * @param {...NullishType[]} vals - Values to be logged if condition is false. 344 * Condition is the first value in vals (if exist) 345 * @returns {void} 346 * @public 347 */ 348 public assert(...vals: NullishType[]): void { 349 if (vals.length == 0) { 350 return 351 } 352 if (!vals[0]) { 353 // replace false evaluated condition with no `vals` length changes 354 let sb = new StringBuilder("Assertion failed") 355 if (vals.length > 1) { 356 sb.append(c':') 357 } 358 vals[0] = sb 359 this.error(...vals) 360 } 361 } 362 363 364 /** 365 * @section Console.count* API 366 */ 367 368 /** 369 * Counts the number of times this method has been called with a specific label 370 * Prints the current count to stdout 371 * @param {string} [label='default'] - The label to identify this counter 372 * @returns {void} 373 */ 374 public count(label?: string): void { 375 const key = label ?? 'default' 376 const current = this.counters.get(key) ?? 0 377 this.counters.set(key, current + 1) 378 this.log(`${key}: ${current + 1}`) 379 } 380 381 /** 382 * Resets the counter for a specific label 383 * @param {string} [label='default'] - The label of the counter to reset 384 * @returns {void} 385 */ 386 public countReset(label?: string): void { 387 const key = label ?? 'default' 388 this.counters.delete(key) 389 } 390 391 392 /** 393 * @section Console.dir* API 394 */ 395 396 397 /** 398 * Prints a formatted representation of an object to stdout 399 * Filters out properties containing 'field#' in their keys 400 * @param {NullishType} obj - The object to inspect 401 * @returns {void} 402 */ 403 public dir(obj?: NullishType): void { 404 if (obj == null || obj == undefined) { 405 return 406 } 407 408 // NOTE (templin.konstantin): Internal Fields Filtering 409 // 410 // In the current language implementation, objects have an internal representation 411 // where some fields are duplicated with a "field#" prefix. These fields are part 412 // of the internal implementation and should not be displayed when outputting objects. 413 // 414 // Example of internal representation: 415 // { 416 // "name": "test", 417 // "field#n": "test", // Internal field 418 // "age": 25, 419 // "field#m": 25 // Internal field 420 // } 421 const filterer = (key: String, value: NullishType): NullishType => { 422 if (key.includes("field#")) { 423 return undefined; 424 } 425 return value; 426 } 427 428 this.log(JSON.stringify(obj, filterer)) 429 } 430 431 /** 432 * Prints an XML representation of an object to stdout 433 * Currently outputs the object as-is 434 * @param {NullishType} obj - The object to display as XML 435 * @returns {void} 436 */ 437 public dirxml(obj: NullishType): void { 438 this.log(obj) 439 } 440 441 442 /** 443 * @section Console.group* API 444 */ 445 446 447 /** 448 * Starts a new logging group with optional label 449 * Increases indentation level for subsequent log messages 450 * @param {string} [objs] - Data to be printed without additional indent 451 * @returns {void} 452 */ 453 public group(...objs: NullishType[]): void { 454 if (objs.length != 0) { 455 this.log(objs) 456 } 457 this.indentationLevel++ 458 } 459 460 /** 461 * Ends the current logging group 462 * Decreases indentation level for subsequent log messages 463 * @returns {void} 464 */ 465 public groupEnd(): void { 466 if (this.indentationLevel > 0) { 467 this.indentationLevel-- 468 } 469 } 470 471 /** 472 * Alias for group() method 473 * Creates a collapsed group in environments that support it 474 * @param {string} [objs] - Data to be printed without additional indent 475 * @returns {void} 476 * @see group 477 */ 478 public groupCollapsed(...objs: NullishType[]): void { 479 this.group(...objs); 480 } 481 482 /** 483 * @section Console methods for tabular data display. 484 */ 485 486 /** 487 * Displays an array of objects in tabular format 488 * Converts the data to a DataFrame and renders it 489 * @param {NullishType[]} data - Array of objects to display as a table 490 * @returns {void} 491 */ 492 public table(...data: NullishType[]): void { 493 if (data.length == 0) { 494 return 495 } 496 497 // Note(ivan-tyulyandin): unwrap from Array, workaround for 25264 498 // unwrap from Array 499 let dat = data[0]! 500 501 const isSimple = (dat == undefined) || (dat == null) 502 || (dat instanceof String) || (Object.keys(__narrowAny(dat)!).length == 0) 503 if (isSimple) { 504 this.log(dat) 505 return 506 } 507 508 let df: DataFrame | undefined = undefined 509 if (dat instanceof ArrayLike) { 510 dat = dat as ArrayLike<NullishType> 511 let d = new Array<NullishType>(dat.length) 512 for (let i = 0; i < d.length; ++i) { 513 d[i] = dat[i] 514 } 515 df = DataFrame.fromObjects(...d) 516 } else { 517 df = DataFrame.fromObjects(dat) 518 } 519 df!.display(); 520 } 521 522 /** 523 * @section Console methods for tabular data display. 524 */ 525 526 527 /** 528 * Starts a timer with an optional label 529 * Used to track execution time between time() and timeEnd() calls 530 * @param {string} [label='default'] - Label to identify the timer 531 * @returns {void} 532 * @see timeEnd 533 * @see timeLog 534 */ 535 public time(label?: string): void { 536 const key = label ?? 'default' 537 if (this.timers.has(key)) { 538 this.warn("⚠️ Warning: Label 'default' already exists for console.time()") 539 return 540 } 541 const start = Date.now().toLong() 542 this.timers.set(key, start) 543 } 544 545 /** 546 * Logs the current duration of a running timer without stopping it 547 * Prints a warning if the specified timer doesn't exist 548 * @param {string} [label='default'] - Label of the timer to check 549 * @returns {void} 550 * @see time 551 */ 552 public timeLog(label?: string): void { 553 const key = label ?? 'default' 554 const startTime = this.timers.get(key) 555 if (startTime === undefined) { 556 this.warn(`Timer '${key}' does not exist`) 557 return 558 } 559 const end = Date.now().toLong() 560 const duration = end - startTime 561 this.log("Start: ", startTime, " end:", end, " diff: ", duration) 562 this.log(`${key}: ${duration}ms`) 563 } 564 565 /** 566 * Stops a timer and logs its final duration 567 * Removes the timer and prints a warning if it doesn't exist 568 * @param {string} [label='default'] - Label of the timer to stop 569 * @returns {void} 570 * @see time 571 */ 572 public timeEnd(label?: string): void { 573 this.timeLog(label) 574 this.timers.delete(label ?? 'default') 575 } 576 577 /** 578 * Prints the current stack trace with an optional label 579 * Skips the first stack frame (the trace call itself) 580 * @param {NullishType[]} ...data - args to prints 581 * @returns {void} 582 */ 583 public trace(...data: NullishType[]): void { 584 this.log("Trace:", data) 585 const stackT = StackTrace.provisionStackTrace(); 586 for (let i = 1; i < stackT.length; ++i) { 587 this.log(stackT[i]); 588 } 589 } 590 591} 592 593// initialized in _initializerBlock_.ets 594export const console: Console; 595 596/** 597 * @section Dataframe helpers class for console.table API 598 */ 599 600/** Column name type alias */ 601type ColumnName = string; 602 603/** Column value type alias */ 604type ColumnValue = string; 605 606/** Array of column values type alias */ 607type ColumnValues = Array<ColumnValue>; 608 609/** Column width type alias */ 610type ColumnWidth = int; 611 612/** 613 * DataFrame class for handling and displaying tabular data 614 * Provides functionality to create, manipulate, and render data in a table format 615 */ 616class DataFrame { 617 /** Map storing column names and their corresponding values */ 618 private tableModel: Map<ColumnName, ColumnValues> 619 620 /** Map storing column names and their display widths */ 621 private columnWidths: Map<ColumnName, ColumnWidth> 622 623 /** Number of rows in the DataFrame */ 624 private rowsCount: int 625 626 /** Header text for the index column */ 627 private static readonly headerIndexRow: string = '| (index) |' 628 629 /** Width of the index column based on header */ 630 private readonly _indexColumnWidth: int = Double.toInt(DataFrame.headerIndexRow.length); 631 632 /** 633 * Creates a new DataFrame with specified columns 634 * @param {Array<ColumnName>} columns - Array of column names 635 * @ensures All columns are initialized with empty value arrays 636 * @ensures Column widths are initialized to the length of column names 637 */ 638 constructor(columns: Array<ColumnName>) { 639 this.tableModel = new Map<ColumnName, ColumnValues>(); 640 this.columnWidths = new Map<ColumnName, ColumnWidth>(); 641 for (let column of columns) { 642 this.tableModel.set(column, new Array<string>()); 643 this.columnWidths.set(column, column.length.toInt()); 644 } 645 this.rowsCount = 0; 646 } 647 648 /** 649 * Inserts a new row of data into the DataFrame 650 * @param {Map<ColumnName, ColumnValue>} row - Map of column names to values 651 * @ensures Column count remains unchanged 652 * @ensures Column widths are updated if new values are longer 653 */ 654 public insertRow(row: Map<ColumnName, ColumnValue>) { 655 for (let key of this.tableModel.keys()) { 656 const value = row.get(key) ?? "-"; 657 this.tableModel.get(key)!.push(value); 658 const currentWidth = this.columnWidths.get(key)!; 659 const valueLength = value.length.toInt(); 660 this.columnWidths.set(key, 661 currentWidth < valueLength 662 ? valueLength 663 : currentWidth); 664 } 665 this.rowsCount++; 666 } 667 668 /** 669 * Builds a border row for the table 670 * @param {string} char - String to use for the border line 671 * @param {string} commonConnector - Character for column separators 672 * @param {string} connectorLeft - Character for left border 673 * @param {string} connectorRight - Character for right border 674 * @returns {string} Formatted border row 675 * @private 676 */ 677 private buildBorderRow( 678 ch: string, 679 commonConnector: string, 680 connectorLeft: string, 681 connectorRight: string, 682 ): string { 683 const connLength: int = 684 this._indexColumnWidth - 685 connectorLeft.length.toInt() - 686 connectorRight.length.toInt(); 687 const conn: string = ch.repeat(connLength); 688 let border: string = `${connectorLeft}${conn}`; 689 for (const col of this.tableModel.keys()) { 690 const colWidth = this.columnWidths.get(col)!; 691 border += ch.repeat(colWidth + 2) + commonConnector; 692 } 693 return `${border}${connectorRight}`; 694 } 695 696 /** 697 * Builds the header row containing column names 698 * @returns {string} Formatted header row 699 * @private 700 */ 701 private buildHeaderRow(): string { 702 let headerRow: string = DataFrame.headerIndexRow; 703 for (const col of this.tableModel.keys()) { 704 const colWidth = this.columnWidths.get(col)!; 705 headerRow += ` ${col.padEnd(colWidth, ' ')} │`; 706 } 707 return headerRow; 708 } 709 710 /** 711 * Builds a data row for the specified index 712 * @param {number} rowIndex - Index of the row to build 713 * @returns {string} Formatted data row 714 * @private 715 */ 716 private buildDataRow(rowIndex: number): string { 717 const paddedIndex = `${rowIndex}`.padEnd(this._indexColumnWidth - 4); 718 let row: string = `│ ${paddedIndex} │`; 719 for (const col of this.tableModel.keys()) { 720 const colWidth = this.columnWidths.get(col)!; 721 const colContent = this.tableModel.get(col)![rowIndex]; 722 row += ` ${colContent.padEnd(colWidth)} │`; 723 } 724 return row; 725 } 726 727 /** 728 * Renders the DataFrame as an array of strings 729 * @returns {Array<String>} Array of formatted table rows 730 */ 731 public render(): Array<String> { 732 if (this.tableModel.size == 0) { 733 return new Array<string>(); 734 } 735 736 // row1 737 const topBorder = this.buildBorderRow('─', '─', '┌', '┐'); 738 // row2 739 const separator = this.buildBorderRow('─', '─', '├', '┤'); 740 // row3 741 const bottomBorder = this.buildBorderRow('─', '─', '└', '┘'); 742 // header has 2 rows and footer - 1 743 744 const output = new Array<string>(4 + this.rowsCount); 745 746 // |output| = 0 747 output[0] = topBorder 748 // |output| = 1 749 output[1] = this.buildHeaderRow() 750 // |output| = 2 751 output[2] = separator 752 // |output| = 3 753 754 for (let i = 0; i < this.rowsCount; i++) { 755 output[3 + i] = this.buildDataRow(i); 756 } 757 // |output| = 3 + rowsCount 758 output[3 + this.rowsCount] = bottomBorder; 759 // |output| = 4 + rowsCount 760 return output; 761 } 762 763 /** 764 * Displays the DataFrame to the console 765 * @returns {void} 766 */ 767 public display(): void { 768 const rows = this.render(); 769 for (const row of rows) { 770 console.log(row); 771 } 772 } 773 774 /** 775 * Converts an object into a map of string key-value pairs for table representation. 776 * Handles various types including primitives, collections, arrays, classes, and functions. 777 * 778 * @param obj - The object to be converted into table info 779 * @returns A Map containing string representations of object properties 780 */ 781 private static getObjectInfoForTable(obj: NullishType): Map<string, string> { 782 const mapping = new Map<string, string>(); 783 const objType = Type.of(obj); 784 const objValue = Value.of(obj); 785 786 // Handle primitive types 787 if (obj === null) { 788 mapping.set("Values", "null"); 789 } else if (obj === undefined) { 790 mapping.set("Values", "undefined"); 791 } else if (obj instanceof String) { 792 mapping.set("Values", `'${obj}'`); 793 } else if (objType.isPrimitive()) { 794 mapping.set("Values", `${obj}`); 795 } 796 797 // Handle complex types 798 else if (obj instanceof Map) { 799 const entries = obj.entries(); 800 for (let entry of entries) { 801 mapping.set(`${entry[0]}`, `${entry[1]}`); 802 } 803 } else if (obj instanceof Set) { 804 const values = obj.values(); 805 let i = 0; 806 for (let value of values) { 807 mapping.set(`{i}?`, `${value}`); 808 i++; 809 } 810 } else if (objType instanceof ArrayType) { 811 const arrayValue = objValue as ArrayValue; 812 for (let i = 0; i < arrayValue.getLength(); i++) { 813 mapping.set(`${i}`, arrayValue.getElement(i).toPrint(10)); 814 } 815 } else if (objType instanceof ClassType) { 816 const arrayValue = objValue as ClassValue; 817 for (let i = 0; i < arrayValue.getFieldsNum(); i++) { 818 try { 819 let fieldName = objType.getField(i).getName() as string; 820 // NOTE (templin.konstantin): replace internal-representation 821 // fields like gensym%% with more human-readable field name 822 // used in interfaces 823 if (fieldName.startsWith("gensym%%")) { 824 fieldName = `Field${i}`; 825 } 826 if (obj instanceof Tuple){ 827 fieldName = `${i}`; 828 } 829 mapping.set( 830 fieldName, 831 arrayValue.getField(i).toPrint(10) 832 ); 833 } catch (e) { 834 continue; 835 } 836 } 837 } else if (objType instanceof FunctionType) { 838 mapping.set("Values", `${objType}`); 839 } else { 840 mapping.set("Values", JSON.stringify(obj)) 841 } 842 843 return mapping; 844 } 845 846 /** 847 * Creates a DataFrame from an array of objects 848 * @param {NullishType[]} data - Array of objects to convert 849 * @returns {DataFrame} New DataFrame instance 850 * @static 851 */ 852 public static fromObjects(...data: NullishType[]): DataFrame { 853 if (data.length == 0) { 854 return new DataFrame(new Array<ColumnName>()); 855 } 856 const columns = new Set<string>(); 857 const preparedInfo = new Array<Map<string, string>>(data.length); 858 for (let i = 0; i < data.length; ++i){ 859 preparedInfo[i] = DataFrame.getObjectInfoForTable(data[i]); 860 for (let key of preparedInfo[i].keys()) 861 { 862 columns.add(key); 863 } 864 } 865 const df = new DataFrame(Array.from(columns)); 866 for (let i = 0; i < data.length; ++i) { 867 df.insertRow(preparedInfo[i]); 868 } 869 return df; 870 } 871 872} 873