• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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