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
);
}
};
})();