Index: src/liveedit-debugger.js |
diff --git a/src/liveedit-debugger.js b/src/liveedit-debugger.js |
index 8fbff41b4cce8c1dfbda47865b7a5c94edbc26c0..59c6ca174c3098bf886b1fbaee9d2570f739386c 100644 |
--- a/src/liveedit-debugger.js |
+++ b/src/liveedit-debugger.js |
@@ -83,7 +83,7 @@ Debug.LiveEdit = new function() { |
// |
// The script is used for compilation, because it produces code that |
// needs to be linked with some particular script (for nested functions). |
- function DebugGatherCompileInfo(source) { |
+ function GatherCompileInfo(source) { |
// Get function info, elements are partially sorted (it is a tree of |
// nested functions serialized as parent followed by serialized children. |
var raw_compile_info = %LiveEditGatherCompileInfo(script, source); |
@@ -92,8 +92,14 @@ Debug.LiveEdit = new function() { |
var compile_info = new Array(); |
var old_index_map = new Array(); |
for (var i = 0; i < raw_compile_info.length; i++) { |
- compile_info.push(new FunctionCompileInfo(raw_compile_info[i])); |
- old_index_map.push(i); |
+ var info = new FunctionCompileInfo(raw_compile_info[i]); |
+ // Remove all links to the actual script. Breakpoints system and |
+ // LiveEdit itself believe that any function in heap that points to a |
+ // particular script is a regular function. |
+ // For some functions we will restore this link later. |
+ %LiveEditFunctionSetScript(info.shared_function_info, void 0); |
+ compile_info.push(info); |
+ old_index_map.push(i); |
} |
for (var i = 0; i < compile_info.length; i++) { |
@@ -148,6 +154,8 @@ Debug.LiveEdit = new function() { |
var shared_infos; |
// Finds SharedFunctionInfo that corresponds compile info with index |
// in old version of the script. |
+ // TODO(LiveEdit): Redesign this part. Find live SharedFunctionInfo |
+ // about the time that FindCorrespondingFunctions is being run. |
function FindFunctionInfo(index) { |
var old_info = old_compile_info[index]; |
for (var i = 0; i < shared_infos.length; i++) { |
@@ -160,15 +168,36 @@ Debug.LiveEdit = new function() { |
} |
// Replaces function's Code. |
- function PatchCode(new_info, shared_info) { |
+ function PatchCode(old_node) { |
+ var new_info = old_node.corresponding_node.info; |
+ var shared_info = FindFunctionInfo(old_node.array_index); |
if (shared_info) { |
%LiveEditReplaceFunctionCode(new_info.raw_array, shared_info.raw_array); |
+ |
+ // The function got a new code. However, this new code brings all new |
+ // instances of SharedFunctionInfo for nested functions. However, |
+ // we want the original instances to be used wherever possible. |
+ // (This is because old instances and new instances will be both |
+ // linked to a script and breakpoints subsystem does not really |
+ // expects this; neither does LiveEdit subsystem on next call). |
+ for (var i = 0; i < old_node.children.length; i++) { |
+ if (old_node.children[i].corresponding_node) { |
+ var corresponding_child = old_node.children[i].corresponding_node; |
+ var child_shared_info = |
+ FindFunctionInfo(old_node.children[i].array_index); |
+ if (child_shared_info) { |
+ %LiveEditReplaceRefToNestedFunction(shared_info.info, |
+ corresponding_child.info.shared_function_info, |
+ child_shared_info.info); |
+ } |
+ } |
+ } |
+ |
change_log.push( {function_patched: new_info.function_name} ); |
} else { |
change_log.push( {function_patched: new_info.function_name, |
function_info_not_found: true} ); |
} |
- |
} |
@@ -180,15 +209,8 @@ Debug.LiveEdit = new function() { |
{ name: old_info.function_name, info_not_found: true } ); |
return; |
} |
- var breakpoint_position_update = %LiveEditPatchFunctionPositions( |
+ %LiveEditPatchFunctionPositions( |
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]; |
- change_log.push( { breakpoint_position_update: |
- { from: break_point_object.source_position(), to: new_pos } } ); |
- break_point_object.updateSourcePosition(new_pos, script); |
- } |
position_patch_report.push( { name: old_info.function_name } ); |
} |
@@ -199,7 +221,7 @@ Debug.LiveEdit = new function() { |
// may access its own text. |
function LinkToOldScript(shared_info, old_info_node) { |
if (shared_info) { |
- %LiveEditRelinkFunctionToScript(shared_info.raw_array, old_script); |
+ %LiveEditFunctionSetScript(shared_info.info, old_script); |
link_to_old_script_report.push( { name: shared_info.function_name } ); |
} else { |
link_to_old_script_report.push( |
@@ -220,12 +242,12 @@ Debug.LiveEdit = new function() { |
} |
// Gather compile information about old version of script. |
- var old_compile_info = DebugGatherCompileInfo(old_source); |
+ var old_compile_info = GatherCompileInfo(old_source); |
// Gather compile information about new version of script. |
var new_compile_info; |
try { |
- new_compile_info = DebugGatherCompileInfo(new_source); |
+ new_compile_info = GatherCompileInfo(new_source); |
} catch (e) { |
throw new Failure("Failed to compile new version of script: " + e); |
} |
@@ -243,6 +265,7 @@ Debug.LiveEdit = new function() { |
// Prepare to-do lists. |
var replace_code_list = new Array(); |
var link_to_old_script_list = new Array(); |
+ var link_to_original_script_list = new Array(); |
var update_positions_list = new Array(); |
function HarvestTodo(old_node) { |
@@ -252,6 +275,15 @@ Debug.LiveEdit = new function() { |
CollectDamaged(node.children[i]); |
} |
} |
+ |
+ // Recursively collects all newly compiled functions that are going into |
+ // business and should be 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]); |
+ CollectNew(node_list[i].children); |
+ } |
+ } |
if (old_node.status == FunctionStatus.DAMAGED) { |
CollectDamaged(old_node); |
@@ -263,6 +295,7 @@ Debug.LiveEdit = new function() { |
update_positions_list.push(old_node); |
} else if (old_node.status == FunctionStatus.CHANGED) { |
replace_code_list.push(old_node); |
+ CollectNew(old_node.unmatched_new_nodes); |
} |
for (var i = 0; i < old_node.children.length; i++) { |
HarvestTodo(old_node.children[i]); |
@@ -286,14 +319,24 @@ Debug.LiveEdit = new function() { |
// We haven't changed anything before this line yet. |
// Committing all changes. |
+ |
+ // Start with breakpoints. Convert their line/column positions and |
+ // temporary remove. |
+ var break_points_restorer = TemporaryRemoveBreakPoints(script, change_log); |
- // Create old script if there are function linked to old version. |
- if (link_to_old_script_list.length > 0) { |
+ var old_script; |
+ |
+ // Create an old script only if there are function that should be linked |
+ // to old version. |
+ if (link_to_old_script_list.length == 0) { |
+ %LiveEditReplaceScript(script, new_source, null); |
+ old_script = void 0; |
+ } else { |
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 = %LiveEditReplaceScript(script, new_source, |
old_script_name); |
var link_to_old_script_report = new Array(); |
@@ -307,10 +350,14 @@ Debug.LiveEdit = new function() { |
} |
} |
+ // Link to an actual script all the functions that we are going to use. |
+ for (var i = 0; i < link_to_original_script_list.length; i++) { |
+ %LiveEditFunctionSetScript( |
+ link_to_original_script_list[i].info.shared_function_info, script); |
+ } |
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)); |
+ PatchCode(replace_code_list[i]); |
} |
var position_patch_report = new Array(); |
@@ -322,10 +369,84 @@ Debug.LiveEdit = new function() { |
PatchPositions(update_positions_list[i].info, |
FindFunctionInfo(update_positions_list[i].array_index)); |
} |
+ |
+ break_points_restorer(pos_translator, old_script); |
} |
// Function is public. |
this.ApplyPatchMultiChunk = ApplyPatchMultiChunk; |
+ |
+ // Returns function that restores breakpoints. |
+ function TemporaryRemoveBreakPoints(original_script, change_log) { |
+ var script_break_points = GetScriptBreakPoints(original_script); |
+ |
+ var break_points_update_report = []; |
+ change_log.push( { break_points_update: break_points_update_report } ); |
+ |
+ var break_point_old_positions = []; |
+ for (var i = 0; i < script_break_points.length; i++) { |
+ var break_point = script_break_points[i]; |
+ |
+ break_point.clear(); |
+ |
+ // TODO(LiveEdit): be careful with resource offset here. |
+ var break_point_position = Debug.findScriptSourcePosition(original_script, |
+ break_point.line(), break_point.column()); |
+ |
+ var old_position_description = { |
+ position: break_point_position, |
+ line: break_point.line(), |
+ column: break_point.column() |
+ } |
+ break_point_old_positions.push(old_position_description); |
+ } |
+ |
+ |
+ // Restores breakpoints and creates their copies in the "old" copy of |
+ // the script. |
+ return function (pos_translator, old_script_copy_opt) { |
+ // Update breakpoints (change positions and restore them in old version |
+ // of script. |
+ for (var i = 0; i < script_break_points.length; i++) { |
+ var break_point = script_break_points[i]; |
+ if (old_script_copy_opt) { |
+ var clone = break_point.cloneForOtherScript(old_script_copy_opt); |
+ clone.set(old_script_copy_opt); |
+ |
+ break_points_update_report.push( { |
+ type: "copied_to_old", |
+ id: break_point.number(), |
+ new_id: clone.number(), |
+ positions: break_point_old_positions[i] |
+ } ); |
+ } |
+ |
+ var updated_position = pos_translator.Translate( |
+ break_point_old_positions[i].position, |
+ PosTranslator.ShiftWithTopInsideChunkHandler); |
+ |
+ var new_location = |
+ original_script.locationFromPosition(updated_position, false); |
+ |
+ break_point.update_positions(new_location.line, new_location.column); |
+ |
+ var new_position_description = { |
+ position: updated_position, |
+ line: new_location.line, |
+ column: new_location.column |
+ } |
+ |
+ break_point.set(original_script); |
+ |
+ break_points_update_report.push( { type: "position_changed", |
+ id: break_point.number(), |
+ old_positions: break_point_old_positions[i], |
+ new_positions: new_position_description |
+ } ); |
+ } |
+ } |
+ } |
+ |
function Assert(condition, message) { |
if (!condition) { |
@@ -346,15 +467,15 @@ Debug.LiveEdit = new function() { |
function PosTranslator(diff_array) { |
var chunks = new Array(); |
- var pos1 = 0; |
- var pos2 = 0; |
+ var current_diff = 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]; |
+ var pos1_begin = diff_array[i]; |
+ var pos2_begin = pos1_begin + current_diff; |
+ var pos1_end = diff_array[i + 1]; |
+ var pos2_end = diff_array[i + 2]; |
+ chunks.push(new DiffChunk(pos1_begin, pos2_begin, pos1_end - pos1_begin, |
+ pos2_end - pos2_begin)); |
+ current_diff = pos2_end - pos1_end; |
} |
this.chunks = chunks; |
} |
@@ -364,14 +485,14 @@ Debug.LiveEdit = new function() { |
PosTranslator.prototype.Translate = function(pos, inside_chunk_handler) { |
var array = this.chunks; |
- if (array.length == 0 || pos < array[0]) { |
+ if (array.length == 0 || pos < array[0].pos1) { |
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; |
+ var middle_index = Math.floor((chunk_index1 + chunk_index2) / 2); |
if (pos < array[middle_index + 1].pos1) { |
chunk_index2 = middle_index; |
} else { |
@@ -380,17 +501,24 @@ Debug.LiveEdit = new function() { |
} |
var chunk = array[chunk_index1]; |
if (pos >= chunk.pos1 + chunk.len1) { |
- return pos += chunk.pos2 + chunk.len2 - 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 = PosTranslator.DefaultInsideChunkHandler; |
} |
- inside_chunk_handler(pos, chunk); |
+ return inside_chunk_handler(pos, chunk); |
} |
- PosTranslator.default_inside_chunk_handler = function() { |
- Assert(false, "Cannot translate position in chaged area"); |
+ PosTranslator.DefaultInsideChunkHandler = function(pos, diff_chunk) { |
+ Assert(false, "Cannot translate position in changed area"); |
+ } |
+ |
+ PosTranslator.ShiftWithTopInsideChunkHandler = |
+ function(pos, diff_chunk) { |
+ // We carelessly do not check whether we stay inside the chunk after |
+ // translation. |
+ return pos - diff_chunk.pos1 + diff_chunk.pos2; |
} |
var FunctionStatus = { |
@@ -412,15 +540,16 @@ Debug.LiveEdit = new function() { |
this.children = children; |
// an index in array of compile_info |
this.array_index = array_index; |
- this.parent = void(0); |
+ 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); |
+ this.status_explanation = void 0; |
+ this.new_start_pos = void 0; |
+ this.new_end_pos = void 0; |
+ this.corresponding_node = void 0; |
+ this.unmatched_new_nodes = void 0; |
} |
// From array of function infos that is implicitly a tree creates |
@@ -564,6 +693,8 @@ Debug.LiveEdit = new function() { |
function ProcessChildren(old_node, new_node) { |
var old_children = old_node.children; |
var new_children = new_node.children; |
+ |
+ var unmatched_new_nodes_list = []; |
var old_index = 0; |
var new_index = 0; |
@@ -573,6 +704,7 @@ Debug.LiveEdit = new function() { |
} else if (new_index < new_children.length) { |
if (new_children[new_index].info.start_position < |
old_children[old_index].new_start_pos) { |
+ 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) { |
@@ -584,6 +716,9 @@ Debug.LiveEdit = new function() { |
ProcessChildren(old_children[old_index], |
new_children[new_index]); |
if (old_children[old_index].status == FunctionStatus.DAMAGED) { |
+ unmatched_new_nodes_list.push( |
+ old_children[old_index].corresponding_node); |
+ old_children[old_index].corresponding_node = void 0; |
old_node.status = FunctionStatus.CHANGED; |
} |
} |
@@ -592,6 +727,7 @@ Debug.LiveEdit = new function() { |
old_children[old_index].status_explanation = |
"No corresponding function in new script found"; |
old_node.status = FunctionStatus.CHANGED; |
+ unmatched_new_nodes_list.push(new_children[new_index]); |
} |
new_index++; |
old_index++; |
@@ -611,12 +747,18 @@ Debug.LiveEdit = new function() { |
} |
} |
+ while (new_index < new_children.length) { |
+ 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)) { |
old_node.status = FunctionStatus.DAMAGED; |
old_node.status_explanation = "Changed code expectations"; |
} |
} |
+ old_node.unmatched_new_nodes = unmatched_new_nodes_list; |
} |
ProcessChildren(old_code_tree, new_code_tree); |
@@ -637,6 +779,7 @@ Debug.LiveEdit = new function() { |
this.code = raw_array[4]; |
this.scope_info = raw_array[5]; |
this.outer_index = raw_array[6]; |
+ this.shared_function_info = raw_array[7]; |
this.next_sibling_index = null; |
this.raw_array = raw_array; |
} |
@@ -776,71 +919,10 @@ Debug.LiveEdit = new function() { |
function CompareStringsLinewise(s1, s2) { |
return %LiveEditCompareStringsLinewise(s1, s2); |
} |
- // Function is public (for tests). |
- this.CompareStringsLinewise = CompareStringsLinewise; |
- |
- // Finds a difference between 2 strings in form of a single chunk. |
- // This is a temporary solution. We should calculate a read diff instead. |
- function FindSimpleDiff(old_source, new_source) { |
- var change_pos; |
- var old_len; |
- var new_len; |
- |
- // A find range block. Whenever control leaves it, it should set 3 local |
- // variables declared above. |
- find_range: |
- { |
- // First look from the beginning of strings. |
- var pos1; |
- { |
- var next_pos; |
- for (pos1 = 0; true; pos1 = next_pos) { |
- if (pos1 >= old_source.length) { |
- change_pos = pos1; |
- old_len = 0; |
- new_len = new_source.length - pos1; |
- break find_range; |
- } |
- if (pos1 >= new_source.length) { |
- change_pos = pos1; |
- old_len = old_source.length - pos1; |
- new_len = 0; |
- break find_range; |
- } |
- if (old_source[pos1] != new_source[pos1]) { |
- break; |
- } |
- next_pos = pos1 + 1; |
- } |
- } |
- // Now compare strings from the ends. |
- change_pos = pos1; |
- var pos_old; |
- var pos_new; |
- { |
- for (pos_old = old_source.length - 1, pos_new = new_source.length - 1; |
- true; |
- pos_old--, pos_new--) { |
- if (pos_old - change_pos + 1 < 0 || pos_new - change_pos + 1 < 0) { |
- old_len = pos_old - change_pos + 2; |
- new_len = pos_new - change_pos + 2; |
- break find_range; |
- } |
- if (old_source[pos_old] != new_source[pos_new]) { |
- old_len = pos_old - change_pos + 1; |
- new_len = pos_new - change_pos + 1; |
- break find_range; |
- } |
- } |
- } |
- } |
- |
- if (old_len == 0 && new_len == 0) { |
- // no change |
- return; |
- } |
- |
- return { "change_pos": change_pos, "old_len": old_len, "new_len": new_len }; |
+ // Functions are public for tests. |
+ this.TestApi = { |
+ PosTranslator: PosTranslator, |
+ CompareStringsLinewise: CompareStringsLinewise |
} |
} |