Index: src/liveedit-debugger.js |
diff --git a/src/liveedit-debugger.js b/src/liveedit-debugger.js |
index d2aee87949b59f363f6e240798b5bf1452bda60a..8fbff41b4cce8c1dfbda47865b7a5c94edbc26c0 100644 |
--- a/src/liveedit-debugger.js |
+++ b/src/liveedit-debugger.js |
@@ -28,26 +28,52 @@ |
// LiveEdit feature implementation. The script should be executed after |
// debug-debugger.js. |
-// A LiveEdit namespace is declared inside a single function constructor. |
+// A LiveEdit namespace. It contains functions that modifies JavaScript code |
+// according to changes of script source (if possible). |
+// |
+// When new script source is put in, the difference is calculated textually, |
+// in form of list of delete/add/change chunks. The functions that include |
+// change chunk(s) get recompiled, or their enclosing functions are |
+// recompiled instead. |
+// If the function may not be recompiled (e.g. it was completely erased in new |
+// version of the script) it remains unchanged, but the code that could |
+// create a new instance of this function goes away. An old version of script |
+// is created to back up this obsolete function. |
+// All unchanged functions have their positions updated accordingly. |
+// |
+// LiveEdit namespace is declared inside a single function constructor. |
Debug.LiveEdit = new function() { |
- // Changes script text and recompiles all relevant functions if possible. |
+ // Applies the change to the script. |
// The change is always a substring (change_pos, change_pos + change_len) |
// being replaced with a completely different string new_str. |
- // |
- // Only one function will have its Code changed in result of this function. |
- // All nested functions (should they have any instances at the moment) are |
- // left unchanged and re-linked to a newly created script instance |
- // representing old version of the source. (Generally speaking, |
- // during the change all nested functions are erased and completely different |
- // set of nested functions are introduced.) All other functions just have |
- // their positions updated. |
+ // This API is a legacy and is obsolete. |
// |
// @param {Script} script that is being changed |
// @param {Array} change_log a list that collects engineer-readable |
// description of what happened. |
function ApplyPatch(script, change_pos, change_len, new_str, |
change_log) { |
+ var old_source = script.source; |
+ |
+ // Prepare new source string. |
+ var new_source = old_source.substring(0, change_pos) + |
+ new_str + old_source.substring(change_pos + change_len); |
+ |
+ return ApplyPatchMultiChunk(script, |
+ [ change_pos, change_pos + change_len, change_pos + new_str.length], |
+ new_source, change_log); |
+ } |
+ // Function is public. |
+ this.ApplyPatch = ApplyPatch; |
+ |
+ // Forward declaration for minifier. |
+ var FunctionStatus; |
+ |
+ // 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) { |
// Fully compiles source string as a script. Returns Array of |
// FunctionCompileInfo -- a descriptions of all functions of the script. |
@@ -117,27 +143,6 @@ Debug.LiveEdit = new function() { |
return compile_info; |
} |
- // Given a positions, finds a function that fully includes the entire |
- // change. |
- function FindChangedFunction(compile_info, offset, len) { |
- // First condition: function should start before the change region. |
- // Function #0 (whole-script function) always does, but we want |
- // one, that is later in this list. |
- var index = 0; |
- while (index + 1 < compile_info.length && |
- compile_info[index + 1].start_position <= offset) { |
- index++; |
- } |
- // Now we are at the last function that begins before the change |
- // region. The function that covers entire change region is either |
- // this function or the enclosing one. |
- for (; compile_info[index].end_position < offset + len; |
- index = compile_info[index].outer_index) { |
- Assert(index != -1); |
- } |
- return index; |
- } |
- |
// Variable forward declarations. Preprocessor "Minifier" needs them. |
var old_compile_info; |
var shared_infos; |
@@ -156,34 +161,27 @@ Debug.LiveEdit = new function() { |
// Replaces function's Code. |
function PatchCode(new_info, shared_info) { |
- %LiveEditReplaceFunctionCode(new_info.raw_array, shared_info.raw_array); |
- |
- change_log.push( {function_patched: new_info.function_name} ); |
- } |
- |
- var change_len_old; |
- var change_len_new; |
- // Translate position in old version of script into position in new |
- // version of script. |
- function PosTranslator(old_pos) { |
- if (old_pos <= change_pos) { |
- return old_pos; |
- } |
- if (old_pos >= change_pos + change_len_old) { |
- return old_pos + change_len_new - change_len_old; |
+ if (shared_info) { |
+ %LiveEditReplaceFunctionCode(new_info.raw_array, shared_info.raw_array); |
+ change_log.push( {function_patched: new_info.function_name} ); |
+ } else { |
+ change_log.push( {function_patched: new_info.function_name, |
+ function_info_not_found: true} ); |
} |
- return -1; |
+ |
} |
- var position_change_array; |
+ |
var position_patch_report; |
- function PatchPositions(new_info, shared_info) { |
+ function PatchPositions(old_info, shared_info) { |
if (!shared_info) { |
- // TODO(LiveEdit): explain what is happening. |
+ // TODO(LiveEdit): function is not compiled yet or is already collected. |
+ position_patch_report.push( |
+ { name: old_info.function_name, info_not_found: true } ); |
return; |
} |
var breakpoint_position_update = %LiveEditPatchFunctionPositions( |
- shared_info.raw_array, position_change_array); |
+ shared_info.raw_array, diff_array); |
for (var i = 0; i < breakpoint_position_update.length; i += 2) { |
var new_pos = breakpoint_position_update[i]; |
var break_point_object = breakpoint_position_update[i + 1]; |
@@ -191,7 +189,7 @@ Debug.LiveEdit = new function() { |
{ from: break_point_object.source_position(), to: new_pos } } ); |
break_point_object.updateSourcePosition(new_pos, script); |
} |
- position_patch_report.push( { name: new_info.function_name } ); |
+ position_patch_report.push( { name: old_info.function_name } ); |
} |
var link_to_old_script_report; |
@@ -199,22 +197,19 @@ Debug.LiveEdit = new function() { |
// Makes a function associated with another instance of a script (the |
// one representing its old version). This way the function still |
// may access its own text. |
- function LinkToOldScript(shared_info) { |
- %LiveEditRelinkFunctionToScript(shared_info.raw_array, old_script); |
- |
- link_to_old_script_report.push( { name: shared_info.function_name } ); |
+ function LinkToOldScript(shared_info, old_info_node) { |
+ if (shared_info) { |
+ %LiveEditRelinkFunctionToScript(shared_info.raw_array, old_script); |
+ link_to_old_script_report.push( { name: shared_info.function_name } ); |
+ } else { |
+ link_to_old_script_report.push( |
+ { name: old_info_node.info.function_name, not_found: true } ); |
+ } |
} |
- |
- |
+ |
var old_source = script.source; |
- var change_len_old = change_len; |
- var change_len_new = new_str.length; |
- |
- // Prepare new source string. |
- var new_source = old_source.substring(0, change_pos) + |
- new_str + old_source.substring(change_pos + change_len); |
- |
+ |
// Find all SharedFunctionInfo's that are compiled from this script. |
var shared_raw_list = %LiveEditFindSharedFunctionInfosForScript(script); |
@@ -234,94 +229,103 @@ Debug.LiveEdit = new function() { |
} catch (e) { |
throw new Failure("Failed to compile new version of script: " + e); |
} |
- |
- // An index of a single function, that is going to have its code replaced. |
- var function_being_patched = |
- FindChangedFunction(old_compile_info, change_pos, change_len_old); |
- |
- // In old and new script versions function with a change should have the |
- // same indexes. |
- var function_being_patched2 = |
- FindChangedFunction(new_compile_info, change_pos, change_len_new); |
- Assert(function_being_patched == function_being_patched2, |
- "inconsistent old/new compile info"); |
- |
- // Check that function being patched has the same expectations in a new |
- // version. Otherwise we cannot safely patch its behavior and should |
- // choose the outer function instead. |
- while (!CompareFunctionExpectations( |
- old_compile_info[function_being_patched], |
- new_compile_info[function_being_patched])) { |
- |
- Assert(old_compile_info[function_being_patched].outer_index == |
- new_compile_info[function_being_patched].outer_index); |
- function_being_patched = |
- old_compile_info[function_being_patched].outer_index; |
- Assert(function_being_patched != -1); |
+ |
+ var pos_translator = new PosTranslator(diff_array); |
+ |
+ // Build tree structures for old and new versions of the script. |
+ var root_old_node = BuildCodeInfoTree(old_compile_info); |
+ var root_new_node = BuildCodeInfoTree(new_compile_info); |
+ |
+ // Analyze changes. |
+ MarkChangedFunctions(root_old_node, pos_translator.GetChunks()); |
+ FindCorrespondingFunctions(root_old_node, root_new_node); |
+ |
+ // Prepare to-do lists. |
+ var replace_code_list = new Array(); |
+ var link_to_old_script_list = new Array(); |
+ var update_positions_list = new Array(); |
+ |
+ function HarvestTodo(old_node) { |
+ function CollectDamaged(node) { |
+ link_to_old_script_list.push(node); |
+ for (var i = 0; i < node.children.length; i++) { |
+ CollectDamaged(node.children[i]); |
+ } |
+ } |
+ |
+ if (old_node.status == FunctionStatus.DAMAGED) { |
+ CollectDamaged(old_node); |
+ return; |
+ } |
+ if (old_node.status == FunctionStatus.UNCHANGED) { |
+ update_positions_list.push(old_node); |
+ } else if (old_node.status == FunctionStatus.SOURCE_CHANGED) { |
+ update_positions_list.push(old_node); |
+ } else if (old_node.status == FunctionStatus.CHANGED) { |
+ replace_code_list.push(old_node); |
+ } |
+ for (var i = 0; i < old_node.children.length; i++) { |
+ HarvestTodo(old_node.children[i]); |
+ } |
} |
- |
+ |
+ HarvestTodo(root_old_node); |
+ |
+ // Collect shared infos for functions whose code need to be patched. |
+ var replaced_function_infos = new Array(); |
+ for (var i = 0; i < replace_code_list.length; i++) { |
+ var info = FindFunctionInfo(replace_code_list[i].array_index); |
+ if (info) { |
+ replaced_function_infos.push(info); |
+ } |
+ } |
+ |
// Check that function being patched is not currently on stack. |
- CheckStackActivations( |
- [ FindFunctionInfo(function_being_patched) ], change_log ); |
+ CheckStackActivations(replaced_function_infos, change_log); |
+ // We haven't changed anything before this line yet. |
// Committing all changes. |
- var old_script_name = CreateNameForOldScript(script); |
- |
- // Update the script text and create a new script representing an old |
- // version of the script. |
- var old_script = %LiveEditReplaceScript(script, new_source, |
- old_script_name); |
- |
- PatchCode(new_compile_info[function_being_patched], |
- FindFunctionInfo(function_being_patched)); |
- |
- var position_patch_report = new Array(); |
- change_log.push( {position_patched: position_patch_report} ); |
- |
- var position_change_array = [ change_pos, |
- change_pos + change_len_old, |
- change_pos + change_len_new ]; |
- |
- // Update positions of all outer functions (i.e. all functions, that |
- // are partially below the function being patched). |
- for (var i = new_compile_info[function_being_patched].outer_index; |
- i != -1; |
- i = new_compile_info[i].outer_index) { |
- PatchPositions(new_compile_info[i], FindFunctionInfo(i)); |
- } |
- |
- // Update positions of all functions that are fully below the function |
- // being patched. |
- var old_next_sibling = |
- old_compile_info[function_being_patched].next_sibling_index; |
- var new_next_sibling = |
- new_compile_info[function_being_patched].next_sibling_index; |
- |
- // We simply go over the tail of both old and new lists. Their tails should |
- // have an identical structure. |
- if (old_next_sibling == -1) { |
- Assert(new_next_sibling == -1); |
- } else { |
- Assert(old_compile_info.length - old_next_sibling == |
- new_compile_info.length - new_next_sibling); |
- |
- for (var i = old_next_sibling, j = new_next_sibling; |
- i < old_compile_info.length; i++, j++) { |
- PatchPositions(new_compile_info[j], FindFunctionInfo(i)); |
+ |
+ // Create old script if there are function linked to old version. |
+ if (link_to_old_script_list.length > 0) { |
+ var old_script_name = CreateNameForOldScript(script); |
+ |
+ // Update the script text and create a new script representing an old |
+ // version of the script. |
+ var old_script = %LiveEditReplaceScript(script, new_source, |
+ old_script_name); |
+ |
+ var link_to_old_script_report = new Array(); |
+ change_log.push( { linked_to_old_script: link_to_old_script_report } ); |
+ |
+ // We need to link to old script all former nested functions. |
+ for (var i = 0; i < link_to_old_script_list.length; i++) { |
+ LinkToOldScript( |
+ FindFunctionInfo(link_to_old_script_list[i].array_index), |
+ link_to_old_script_list[i]); |
} |
} |
+ |
+ |
+ for (var i = 0; i < replace_code_list.length; i++) { |
+ PatchCode(replace_code_list[i].corresponding_node.info, |
+ FindFunctionInfo(replace_code_list[i].array_index)); |
+ } |
- var link_to_old_script_report = new Array(); |
- change_log.push( { linked_to_old_script: link_to_old_script_report } ); |
- |
- // We need to link to old script all former nested functions. |
- for (var i = function_being_patched + 1; i < old_next_sibling; i++) { |
- LinkToOldScript(FindFunctionInfo(i), old_script); |
+ var position_patch_report = new Array(); |
+ change_log.push( {position_patched: position_patch_report} ); |
+ |
+ for (var i = 0; i < update_positions_list.length; i++) { |
+ // TODO(LiveEdit): take into account wether it's source_changed or |
+ // unchanged and whether positions changed at all. |
+ PatchPositions(update_positions_list[i].info, |
+ FindFunctionInfo(update_positions_list[i].array_index)); |
} |
} |
// Function is public. |
- this.ApplyPatch = ApplyPatch; |
+ this.ApplyPatchMultiChunk = ApplyPatchMultiChunk; |
+ |
function Assert(condition, message) { |
if (!condition) { |
@@ -332,6 +336,296 @@ Debug.LiveEdit = new function() { |
} |
} |
} |
+ |
+ function DiffChunk(pos1, pos2, len1, len2) { |
+ this.pos1 = pos1; |
+ this.pos2 = pos2; |
+ this.len1 = len1; |
+ this.len2 = len2; |
+ } |
+ |
+ function PosTranslator(diff_array) { |
+ var chunks = new Array(); |
+ var pos1 = 0; |
+ var pos2 = 0; |
+ for (var i = 0; i < diff_array.length; i += 3) { |
+ pos2 += diff_array[i] - pos1 + pos2; |
+ pos1 = diff_array[i]; |
+ chunks.push(new DiffChunk(pos1, pos2, diff_array[i + 1] - pos1, |
+ diff_array[i + 2] - pos2)); |
+ pos1 = diff_array[i + 1]; |
+ pos2 = diff_array[i + 2]; |
+ } |
+ this.chunks = chunks; |
+ } |
+ PosTranslator.prototype.GetChunks = function() { |
+ return this.chunks; |
+ } |
+ |
+ PosTranslator.prototype.Translate = function(pos, inside_chunk_handler) { |
+ var array = this.chunks; |
+ if (array.length == 0 || pos < array[0]) { |
+ return pos; |
+ } |
+ var chunk_index1 = 0; |
+ var chunk_index2 = array.length - 1; |
+ |
+ while (chunk_index1 < chunk_index2) { |
+ var middle_index = (chunk_index1 + chunk_index2) / 2; |
+ if (pos < array[middle_index + 1].pos1) { |
+ chunk_index2 = middle_index; |
+ } else { |
+ chunk_index1 = middle_index + 1; |
+ } |
+ } |
+ var chunk = array[chunk_index1]; |
+ if (pos >= chunk.pos1 + chunk.len1) { |
+ return pos += chunk.pos2 + chunk.len2 - chunk.pos1 - chunk.len1; |
+ } |
+ |
+ if (!inside_chunk_handler) { |
+ inside_chunk_handler = PosTranslator.default_inside_chunk_handler; |
+ } |
+ inside_chunk_handler(pos, chunk); |
+ } |
+ |
+ PosTranslator.default_inside_chunk_handler = function() { |
+ Assert(false, "Cannot translate position in chaged area"); |
+ } |
+ |
+ var FunctionStatus = { |
+ // No change to function or its inner functions; however its positions |
+ // in script may have been shifted. |
+ UNCHANGED: "unchanged", |
+ // The code of a function remains unchanged, but something happened inside |
+ // some inner functions. |
+ SOURCE_CHANGED: "source changed", |
+ // The code of a function is changed or some nested function cannot be |
+ // properly patched so this function must be recompiled. |
+ CHANGED: "changed", |
+ // Function is changed but cannot be patched. |
+ DAMAGED: "damaged" |
+ } |
+ |
+ function CodeInfoTreeNode(code_info, children, array_index) { |
+ this.info = code_info; |
+ this.children = children; |
+ // an index in array of compile_info |
+ this.array_index = array_index; |
+ this.parent = void(0); |
+ |
+ this.status = FunctionStatus.UNCHANGED; |
+ // Status explanation is used for debugging purposes and will be shown |
+ // in user UI if some explanations are needed. |
+ this.status_explanation = void(0); |
+ this.new_start_pos = void(0); |
+ this.new_end_pos = void(0); |
+ this.corresponding_node = void(0); |
+ } |
+ |
+ // From array of function infos that is implicitly a tree creates |
+ // an actual tree of functions in script. |
+ function BuildCodeInfoTree(code_info_array) { |
+ // Throughtout all function we iterate over input array. |
+ var index = 0; |
+ |
+ // Recursive function that builds a branch of tree. |
+ function BuildNode() { |
+ var my_index = index; |
+ index++; |
+ var child_array = new Array(); |
+ while (index < code_info_array.length && |
+ code_info_array[index].outer_index == my_index) { |
+ child_array.push(BuildNode()); |
+ } |
+ var node = new CodeInfoTreeNode(code_info_array[my_index], child_array, |
+ my_index); |
+ for (var i = 0; i < child_array.length; i++) { |
+ child_array[i].parent = node; |
+ } |
+ return node; |
+ } |
+ |
+ var root = BuildNode(); |
+ Assert(index == code_info_array.length); |
+ return root; |
+ } |
+ |
+ // Applies a list of the textual diff chunks onto the tree of functions. |
+ // Determines status of each function (from unchanged to damaged). However |
+ // children of unchanged functions are ignored. |
+ function MarkChangedFunctions(code_info_tree, chunks) { |
+ |
+ // A convenient interator over diff chunks that also translates |
+ // positions from old to new in a current non-changed part of script. |
+ var chunk_it = new function() { |
+ var chunk_index = 0; |
+ var pos_diff = 0; |
+ this.current = function() { return chunks[chunk_index]; } |
+ this.next = function() { |
+ var chunk = chunks[chunk_index]; |
+ pos_diff = chunk.pos2 + chunk.len2 - (chunk.pos1 + chunk.len1); |
+ chunk_index++; |
+ } |
+ this.done = function() { return chunk_index >= chunks.length; } |
+ this.TranslatePos = function(pos) { return pos + pos_diff; } |
+ }; |
+ |
+ // A recursive function that processes internals of a function and all its |
+ // inner functions. Iterator chunk_it initially points to a chunk that is |
+ // below function start. |
+ function ProcessInternals(info_node) { |
+ info_node.new_start_pos = chunk_it.TranslatePos( |
+ info_node.info.start_position); |
+ var child_index = 0; |
+ var code_changed = false; |
+ var source_changed = false; |
+ // Simultaneously iterates over child functions and over chunks. |
+ while (!chunk_it.done() && |
+ chunk_it.current().pos1 < info_node.info.end_position) { |
+ if (child_index < info_node.children.length) { |
+ var child = info_node.children[child_index]; |
+ |
+ if (child.info.end_position <= chunk_it.current().pos1) { |
+ ProcessUnchangedChild(child); |
+ child_index++; |
+ continue; |
+ } else if (child.info.start_position >= |
+ chunk_it.current().pos1 + chunk_it.current().len1) { |
+ code_changed = true; |
+ chunk_it.next(); |
+ continue; |
+ } else if (child.info.start_position <= chunk_it.current().pos1 && |
+ child.info.end_position >= chunk_it.current().pos1 + |
+ chunk_it.current().len1) { |
+ ProcessInternals(child); |
+ source_changed = source_changed || |
+ ( child.status != FunctionStatus.UNCHANGED ); |
+ code_changed = code_changed || |
+ ( child.status == FunctionStatus.DAMAGED ); |
+ child_index++; |
+ continue; |
+ } else { |
+ code_changed = true; |
+ child.status = FunctionStatus.DAMAGED; |
+ child.status_explanation = |
+ "Text diff overlaps with function boundary"; |
+ child_index++; |
+ continue; |
+ } |
+ } else { |
+ if (chunk_it.current().pos1 + chunk_it.current().len1 <= |
+ info_node.info.end_position) { |
+ info_node.status = FunctionStatus.CHANGED; |
+ chunk_it.next(); |
+ continue; |
+ } else { |
+ info_node.status = FunctionStatus.DAMAGED; |
+ info_node.status_explanation = |
+ "Text diff overlaps with function boundary"; |
+ return; |
+ } |
+ } |
+ Assert("Unreachable", false); |
+ } |
+ while (child_index < info_node.children.length) { |
+ var child = info_node.children[child_index]; |
+ ProcessUnchangedChild(child); |
+ child_index++; |
+ } |
+ if (code_changed) { |
+ info_node.status = FunctionStatus.CHANGED; |
+ } else if (source_changed) { |
+ info_node.status = FunctionStatus.SOURCE_CHANGED; |
+ } |
+ info_node.new_end_pos = |
+ chunk_it.TranslatePos(info_node.info.end_position); |
+ } |
+ |
+ function ProcessUnchangedChild(node) { |
+ node.new_start_pos = chunk_it.TranslatePos(node.info.start_position); |
+ node.new_end_pos = chunk_it.TranslatePos(node.info.end_position); |
+ } |
+ |
+ ProcessInternals(code_info_tree); |
+ } |
+ |
+ // For ecah old function (if it is not damaged) tries to find a corresponding |
+ // function in new script. Typically it should succeed (non-damaged functions |
+ // by definition may only have changes inside their bodies). However there are |
+ // reasons for corresponence not to be found; function with unmodified text |
+ // in new script may become enclosed into other function; the innocent change |
+ // inside function body may in fact be something like "} function B() {" that |
+ // splits a function into 2 functions. |
+ function FindCorrespondingFunctions(old_code_tree, new_code_tree) { |
+ |
+ // A recursive function that tries to find a correspondence for all |
+ // child functions and for their inner functions. |
+ function ProcessChildren(old_node, new_node) { |
+ var old_children = old_node.children; |
+ var new_children = new_node.children; |
+ |
+ var old_index = 0; |
+ var new_index = 0; |
+ while (old_index < old_children.length) { |
+ if (old_children[old_index].status == FunctionStatus.DAMAGED) { |
+ old_index++; |
+ } else if (new_index < new_children.length) { |
+ if (new_children[new_index].info.start_position < |
+ old_children[old_index].new_start_pos) { |
+ new_index++; |
+ } else if (new_children[new_index].info.start_position == |
+ old_children[old_index].new_start_pos) { |
+ if (new_children[new_index].info.end_position == |
+ old_children[old_index].new_end_pos) { |
+ old_children[old_index].corresponding_node = |
+ new_children[new_index]; |
+ if (old_children[old_index].status != FunctionStatus.UNCHANGED) { |
+ ProcessChildren(old_children[old_index], |
+ new_children[new_index]); |
+ if (old_children[old_index].status == FunctionStatus.DAMAGED) { |
+ old_node.status = FunctionStatus.CHANGED; |
+ } |
+ } |
+ } else { |
+ old_children[old_index].status = FunctionStatus.DAMAGED; |
+ old_children[old_index].status_explanation = |
+ "No corresponding function in new script found"; |
+ old_node.status = FunctionStatus.CHANGED; |
+ } |
+ new_index++; |
+ old_index++; |
+ } else { |
+ old_children[old_index].status = FunctionStatus.DAMAGED; |
+ old_children[old_index].status_explanation = |
+ "No corresponding function in new script found"; |
+ old_node.status = FunctionStatus.CHANGED; |
+ old_index++; |
+ } |
+ } else { |
+ old_children[old_index].status = FunctionStatus.DAMAGED; |
+ old_children[old_index].status_explanation = |
+ "No corresponding function in new script found"; |
+ old_node.status = FunctionStatus.CHANGED; |
+ old_index++; |
+ } |
+ } |
+ |
+ if (old_node.status == FunctionStatus.CHANGED) { |
+ if (!CompareFunctionExpectations(old_node.info, new_node.info)) { |
+ old_node.status = FunctionStatus.DAMAGED; |
+ old_node.status_explanation = "Changed code expectations"; |
+ } |
+ } |
+ } |
+ |
+ ProcessChildren(old_code_tree, new_code_tree); |
+ |
+ old_code_tree.corresponding_node = new_code_tree; |
+ Assert(old_code_tree.status != FunctionStatus.DAMAGED, |
+ "Script became damaged"); |
+ } |
+ |
// An object describing function compilation details. Its index fields |
// apply to indexes inside array that stores these objects. |
@@ -469,13 +763,12 @@ Debug.LiveEdit = new function() { |
// LiveEdit main entry point: changes a script text to a new string. |
function SetScriptSource(script, new_source, change_log) { |
var old_source = script.source; |
- var diff = FindSimpleDiff(old_source, new_source); |
- if (!diff) { |
+ var diff = CompareStringsLinewise(old_source, new_source); |
+ if (diff.length == 0) { |
+ change_log.push( { empty_diff: true } ); |
return; |
} |
- ApplyPatch(script, diff.change_pos, diff.old_len, |
- new_source.substring(diff.change_pos, diff.change_pos + diff.new_len), |
- change_log); |
+ ApplyPatchMultiChunk(script, diff, new_source, change_log); |
} |
// Function is public. |
this.SetScriptSource = SetScriptSource; |