| Index: src/liveedit-debugger.js
|
| diff --git a/src/liveedit-debugger.js b/src/liveedit-debugger.js
|
| index 34d5c0da89a1a038b6c306b8dd89cd845cb13e5f..c8c6f082c03297d28ca895c8b70cd73345db1197 100644
|
| --- a/src/liveedit-debugger.js
|
| +++ b/src/liveedit-debugger.js
|
| @@ -51,7 +51,8 @@ Debug.LiveEdit = new function() {
|
| // Applies the change to the script.
|
| // The change is in form of list of chunks encoded in a single array as
|
| // a series of triplets (pos1_start, pos1_end, pos2_end)
|
| - function ApplyPatchMultiChunk(script, diff_array, new_source, change_log) {
|
| + function ApplyPatchMultiChunk(script, diff_array, new_source, preview_only,
|
| + change_log) {
|
|
|
| var old_source = script.source;
|
|
|
| @@ -96,7 +97,7 @@ Debug.LiveEdit = new function() {
|
| }
|
|
|
| // Recursively collects all newly compiled functions that are going into
|
| - // business and should be have link to the actual script updated.
|
| + // business and should have link to the actual script updated.
|
| function CollectNew(node_list) {
|
| for (var i = 0; i < node_list.length; i++) {
|
| link_to_original_script_list.push(node_list[i]);
|
| @@ -121,6 +122,20 @@ Debug.LiveEdit = new function() {
|
| }
|
| }
|
|
|
| + var preview_description = {
|
| + change_tree: DescribeChangeTree(root_old_node),
|
| + textual_diff: {
|
| + old_len: old_source.length,
|
| + new_len: new_source.length,
|
| + chunks: diff_array
|
| + },
|
| + updated: false
|
| + };
|
| +
|
| + if (preview_only) {
|
| + return preview_description;
|
| + }
|
| +
|
| HarvestTodo(root_old_node);
|
|
|
| // Collect shared infos for functions whose code need to be patched.
|
| @@ -132,13 +147,15 @@ Debug.LiveEdit = new function() {
|
| }
|
| }
|
|
|
| - // Check that function being patched is not currently on stack.
|
| - CheckStackActivations(replaced_function_infos, change_log);
|
| -
|
| -
|
| // We haven't changed anything before this line yet.
|
| // Committing all changes.
|
|
|
| + // Check that function being patched is not currently on stack or drop them.
|
| + var dropped_functions_number =
|
| + CheckStackActivations(replaced_function_infos, change_log);
|
| +
|
| + preview_description.stack_modified = dropped_functions_number != 0;
|
| +
|
| // Start with breakpoints. Convert their line/column positions and
|
| // temporary remove.
|
| var break_points_restorer = TemporaryRemoveBreakPoints(script, change_log);
|
| @@ -166,6 +183,8 @@ Debug.LiveEdit = new function() {
|
| LinkToOldScript(link_to_old_script_list[i], old_script,
|
| link_to_old_script_report);
|
| }
|
| +
|
| + preview_description.created_script_name = old_script_name;
|
| }
|
|
|
| // Link to an actual script all the functions that we are going to use.
|
| @@ -189,6 +208,9 @@ Debug.LiveEdit = new function() {
|
| }
|
|
|
| break_points_restorer(pos_translator, old_script);
|
| +
|
| + preview_description.updated = true;
|
| + return preview_description;
|
| }
|
| // Function is public.
|
| this.ApplyPatchMultiChunk = ApplyPatchMultiChunk;
|
| @@ -494,6 +516,16 @@ Debug.LiveEdit = new function() {
|
| this.new_end_pos = void 0;
|
| this.corresponding_node = void 0;
|
| this.unmatched_new_nodes = void 0;
|
| +
|
| + // 'Textual' correspondence/matching is weaker than 'pure'
|
| + // correspondence/matching. We need 'textual' level for visual presentation
|
| + // in UI, we use 'pure' level for actual code manipulation.
|
| + // Sometimes only function body is changed (functions in old and new script
|
| + // textually correspond), but we cannot patch the code, so we see them
|
| + // as an old function deleted and new function created.
|
| + this.textual_corresponding_node = void 0;
|
| + this.textually_unmatched_new_nodes = void 0;
|
| +
|
| this.live_shared_info_wrapper = void 0;
|
| }
|
|
|
| @@ -640,6 +672,7 @@ Debug.LiveEdit = new function() {
|
| var new_children = new_node.children;
|
|
|
| var unmatched_new_nodes_list = [];
|
| + var textually_unmatched_new_nodes_list = [];
|
|
|
| var old_index = 0;
|
| var new_index = 0;
|
| @@ -650,6 +683,7 @@ Debug.LiveEdit = new function() {
|
| if (new_children[new_index].info.start_position <
|
| old_children[old_index].new_start_pos) {
|
| unmatched_new_nodes_list.push(new_children[new_index]);
|
| + textually_unmatched_new_nodes_list.push(new_children[new_index]);
|
| new_index++;
|
| } else if (new_children[new_index].info.start_position ==
|
| old_children[old_index].new_start_pos) {
|
| @@ -657,6 +691,8 @@ Debug.LiveEdit = new function() {
|
| old_children[old_index].new_end_pos) {
|
| old_children[old_index].corresponding_node =
|
| new_children[new_index];
|
| + old_children[old_index].textual_corresponding_node =
|
| + new_children[new_index];
|
| if (old_children[old_index].status != FunctionStatus.UNCHANGED) {
|
| ProcessChildren(old_children[old_index],
|
| new_children[new_index]);
|
| @@ -673,6 +709,7 @@ Debug.LiveEdit = new function() {
|
| "No corresponding function in new script found";
|
| old_node.status = FunctionStatus.CHANGED;
|
| unmatched_new_nodes_list.push(new_children[new_index]);
|
| + textually_unmatched_new_nodes_list.push(new_children[new_index]);
|
| }
|
| new_index++;
|
| old_index++;
|
| @@ -694,21 +731,28 @@ Debug.LiveEdit = new function() {
|
|
|
| while (new_index < new_children.length) {
|
| unmatched_new_nodes_list.push(new_children[new_index]);
|
| + textually_unmatched_new_nodes_list.push(new_children[new_index]);
|
| new_index++;
|
| }
|
|
|
| if (old_node.status == FunctionStatus.CHANGED) {
|
| - if (!CompareFunctionExpectations(old_node.info, new_node.info)) {
|
| + var why_wrong_expectations =
|
| + WhyFunctionExpectationsDiffer(old_node.info, new_node.info);
|
| + if (why_wrong_expectations) {
|
| old_node.status = FunctionStatus.DAMAGED;
|
| - old_node.status_explanation = "Changed code expectations";
|
| + old_node.status_explanation = why_wrong_expectations;
|
| }
|
| }
|
| old_node.unmatched_new_nodes = unmatched_new_nodes_list;
|
| + old_node.textually_unmatched_new_nodes =
|
| + textually_unmatched_new_nodes_list;
|
| }
|
|
|
| ProcessChildren(old_code_tree, new_code_tree);
|
|
|
| old_code_tree.corresponding_node = new_code_tree;
|
| + old_code_tree.textual_corresponding_node = new_code_tree;
|
| +
|
| Assert(old_code_tree.status != FunctionStatus.DAMAGED,
|
| "Script became damaged");
|
| }
|
| @@ -792,27 +836,37 @@ Debug.LiveEdit = new function() {
|
| }
|
|
|
| // Compares a function interface old and new version, whether it
|
| - // changed or not.
|
| - function CompareFunctionExpectations(function_info1, function_info2) {
|
| + // changed or not. Returns explanation if they differ.
|
| + function WhyFunctionExpectationsDiffer(function_info1, function_info2) {
|
| // Check that function has the same number of parameters (there may exist
|
| // an adapter, that won't survive function parameter number change).
|
| if (function_info1.param_num != function_info2.param_num) {
|
| - return false;
|
| + return "Changed parameter number: " + function_info1.param_num +
|
| + " and " + function_info2.param_num;
|
| }
|
| var scope_info1 = function_info1.scope_info;
|
| var scope_info2 = function_info2.scope_info;
|
| -
|
| - if (!scope_info1) {
|
| - return !scope_info2;
|
| +
|
| + var scope_info1_text;
|
| + var scope_info2_text;
|
| +
|
| + if (scope_info1) {
|
| + scope_info1_text = scope_info1.toString();
|
| + } else {
|
| + scope_info1_text = "";
|
| }
|
| -
|
| - if (scope_info1.length != scope_info2.length) {
|
| - return false;
|
| + if (scope_info2) {
|
| + scope_info2_text = scope_info2.toString();
|
| + } else {
|
| + scope_info2_text = "";
|
| }
|
| -
|
| - // Check that outer scope structure is not changed. Otherwise the function
|
| - // will not properly work with existing scopes.
|
| - return scope_info1.toString() == scope_info2.toString();
|
| +
|
| + if (scope_info1_text != scope_info2_text) {
|
| + return "Incompatible variable maps: [" + scope_info1_text +
|
| + "] and [" + scope_info2_text + "]";
|
| + }
|
| + // No differences. Return undefined.
|
| + return;
|
| }
|
|
|
| // Minifier forward declaration.
|
| @@ -856,6 +910,8 @@ Debug.LiveEdit = new function() {
|
| change_log.push( { functions_on_stack: problems } );
|
| throw new Failure("Blocked by functions on stack");
|
| }
|
| +
|
| + return dropped.length;
|
| }
|
|
|
| // A copy of the FunctionPatchabilityStatus enum from liveedit.h
|
| @@ -897,14 +953,11 @@ Debug.LiveEdit = new function() {
|
| this.GetPcFromSourcePos = GetPcFromSourcePos;
|
|
|
| // LiveEdit main entry point: changes a script text to a new string.
|
| - function SetScriptSource(script, new_source, change_log) {
|
| + function SetScriptSource(script, new_source, preview_only, change_log) {
|
| var old_source = script.source;
|
| var diff = CompareStringsLinewise(old_source, new_source);
|
| - if (diff.length == 0) {
|
| - change_log.push( { empty_diff: true } );
|
| - return;
|
| - }
|
| - ApplyPatchMultiChunk(script, diff, new_source, change_log);
|
| + return ApplyPatchMultiChunk(script, diff, new_source, preview_only,
|
| + change_log);
|
| }
|
| // Function is public.
|
| this.SetScriptSource = SetScriptSource;
|
| @@ -931,7 +984,67 @@ Debug.LiveEdit = new function() {
|
|
|
| return ApplyPatchMultiChunk(script,
|
| [ change_pos, change_pos + change_len, change_pos + new_str.length],
|
| - new_source, change_log);
|
| + new_source, false, change_log);
|
| + }
|
| +
|
| + // Creates JSON description for a change tree.
|
| + function DescribeChangeTree(old_code_tree) {
|
| +
|
| + function ProcessOldNode(node) {
|
| + var child_infos = [];
|
| + for (var i = 0; i < node.children.length; i++) {
|
| + var child = node.children[i];
|
| + if (child.status != FunctionStatus.UNCHANGED) {
|
| + child_infos.push(ProcessOldNode(child));
|
| + }
|
| + }
|
| + var new_child_infos = [];
|
| + if (node.textually_unmatched_new_nodes) {
|
| + for (var i = 0; i < node.textually_unmatched_new_nodes.length; i++) {
|
| + var child = node.textually_unmatched_new_nodes[i];
|
| + new_child_infos.push(ProcessNewNode(child));
|
| + }
|
| + }
|
| + var res = {
|
| + name: node.info.function_name,
|
| + positions: DescribePositions(node),
|
| + status: node.status,
|
| + children: child_infos,
|
| + new_children: new_child_infos
|
| + };
|
| + if (node.status_explanation) {
|
| + res.status_explanation = node.status_explanation;
|
| + }
|
| + if (node.textual_corresponding_node) {
|
| + res.new_positions = DescribePositions(node.textual_corresponding_node);
|
| + }
|
| + return res;
|
| + }
|
| +
|
| + function ProcessNewNode(node) {
|
| + var child_infos = [];
|
| + // Do not list ancestors.
|
| + if (false) {
|
| + for (var i = 0; i < node.children.length; i++) {
|
| + child_infos.push(ProcessNewNode(node.children[i]));
|
| + }
|
| + }
|
| + var res = {
|
| + name: node.info.function_name,
|
| + positions: DescribePositions(node),
|
| + children: child_infos,
|
| + };
|
| + return res;
|
| + }
|
| +
|
| + function DescribePositions(node) {
|
| + return {
|
| + start_position: node.info.start_position,
|
| + end_position: node.info.end_position
|
| + };
|
| + }
|
| +
|
| + return ProcessOldNode(old_code_tree);
|
| }
|
|
|
|
|
|
|