const tracing = (() => { const log = []; const objects = new Map(); let call_stack_top = null; function record_value(log_entry, name, value, as_array=false) { log_entry[name] = value; for (const _value of as_array? value : [value]) { if (_value !== null && _value !== undefined && _value !== false && _value !== true) { const relevant_log_entries = objects.get(_value) || []; relevant_log_entries.push(log_entry); objects.set(_value, relevant_log_entries); } } return value; } function with_log_entry(op_name, line, column, cb) { const log_entry = { op_name, line, column, id: log.length, parent_call: call_stack_top }; const saved_stack_top = call_stack_top; log.push(log_entry); call_stack_top = log_entry; try { return cb(log_entry); } catch(ex) { record_value(log_entry, "error", ex); throw ex; } finally { call_stack_top = saved_stack_top; } } return { get_objects: () => objects, get_log: () => log, record_binary: function(line, column, operation_name, operation, left_producer, right_producer) { function go(log_entry) { const left = record_value(log_entry, "left", left_producer()); const right = record_value(log_entry, "right", right_producer()); const result = operation(left, right); return record_value(log_entry, "result", result); } return with_log_entry(operation_name, line, column, go); }, record_lazy_binary: function(line, column, operation_name, operation, left_producer, right_producer) { function go(log_entry) { const left = record_value(log_entry, "left", left_producer()); const result = operation(left, right_producer); return record_value(log_entry, "result", result); } return with_log_entry(operation_name, line, column, go); }, record_call: function(line, column, function_producer, optional, args_producer) { function go(log_entry) { const function_object = record_value(log_entry, "function_object", function_producer()); const record_args = () => record_value(log_entry, "args", args_producer(), true); const result = optional ? function_object?.(...record_args()) : function_object(...record_args()); return record_value(log_entry, "result", result); } return with_log_entry("call", line, column, go); }, record_prop_call: function(line, column, this_producer, property_optional, property_producer, optional, args_producer) { function go(log_entry) { const bound_this = record_value(log_entry, "bound_this", this_producer()); const record_property = () => record_value( log_entry, "property", property_producer() ); const function_object = record_value(log_entry, "function_object", property_optional ? bound_this?.[record_property()] : bound_this[record_property()]); if ((function_object === null || function_object === undefined) && optional) return undefined; const record_args = () => record_value(log_entry, "args", args_producer(), true); const result = Function.apply.call(function_object, bound_this, record_args()); return record_value(log_entry, "result", result); } return with_log_entry( `obj${property_optional}[prop]${optional}() call`, line, column, go ); } }; })();