Add fine-grained diff implementation to LiveEdit engine.
BUG=1013 TEST= Review URL: http://codereview.chromium.org/6017008 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@6274 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
parent
c1d4d89a31
commit
7c468e53c4
@ -980,15 +980,15 @@ Debug.LiveEdit = new function() {
|
||||
// LiveEdit main entry point: changes a script text to a new string.
|
||||
function SetScriptSource(script, new_source, preview_only, change_log) {
|
||||
var old_source = script.source;
|
||||
var diff = CompareStringsLinewise(old_source, new_source);
|
||||
var diff = CompareStrings(old_source, new_source);
|
||||
return ApplyPatchMultiChunk(script, diff, new_source, preview_only,
|
||||
change_log);
|
||||
}
|
||||
// Function is public.
|
||||
this.SetScriptSource = SetScriptSource;
|
||||
|
||||
function CompareStringsLinewise(s1, s2) {
|
||||
return %LiveEditCompareStringsLinewise(s1, s2);
|
||||
function CompareStrings(s1, s2) {
|
||||
return %LiveEditCompareStrings(s1, s2);
|
||||
}
|
||||
|
||||
// Applies the change to the script.
|
||||
@ -1076,7 +1076,7 @@ Debug.LiveEdit = new function() {
|
||||
// Functions are public for tests.
|
||||
this.TestApi = {
|
||||
PosTranslator: PosTranslator,
|
||||
CompareStringsLinewise: CompareStringsLinewise,
|
||||
CompareStrings: CompareStrings,
|
||||
ApplySingleChunkPatch: ApplySingleChunkPatch
|
||||
}
|
||||
}
|
||||
|
123
src/liveedit.cc
123
src/liveedit.cc
@ -275,6 +275,82 @@ static bool CompareSubstrings(Handle<String> s1, int pos1,
|
||||
}
|
||||
|
||||
|
||||
// A helper class that writes chunk numbers into JSArray.
|
||||
// Each chunk is stored as 3 array elements: (pos1_begin, pos1_end, pos2_end).
|
||||
class CompareOutputArrayWriter {
|
||||
public:
|
||||
CompareOutputArrayWriter()
|
||||
: array_(Factory::NewJSArray(10)), current_size_(0) {}
|
||||
|
||||
Handle<JSArray> GetResult() {
|
||||
return array_;
|
||||
}
|
||||
|
||||
void WriteChunk(int char_pos1, int char_pos2, int char_len1, int char_len2) {
|
||||
SetElement(array_, current_size_, Handle<Object>(Smi::FromInt(char_pos1)));
|
||||
SetElement(array_, current_size_ + 1,
|
||||
Handle<Object>(Smi::FromInt(char_pos1 + char_len1)));
|
||||
SetElement(array_, current_size_ + 2,
|
||||
Handle<Object>(Smi::FromInt(char_pos2 + char_len2)));
|
||||
current_size_ += 3;
|
||||
}
|
||||
|
||||
private:
|
||||
Handle<JSArray> array_;
|
||||
int current_size_;
|
||||
};
|
||||
|
||||
|
||||
// Represents 2 strings as 2 arrays of tokens.
|
||||
// TODO(LiveEdit): Currently it's actually an array of charactres.
|
||||
// Make array of tokens instead.
|
||||
class TokensCompareInput : public Comparator::Input {
|
||||
public:
|
||||
TokensCompareInput(Handle<String> s1, int offset1, int len1,
|
||||
Handle<String> s2, int offset2, int len2)
|
||||
: s1_(s1), offset1_(offset1), len1_(len1),
|
||||
s2_(s2), offset2_(offset2), len2_(len2) {
|
||||
}
|
||||
virtual int getLength1() {
|
||||
return len1_;
|
||||
}
|
||||
virtual int getLength2() {
|
||||
return len2_;
|
||||
}
|
||||
bool equals(int index1, int index2) {
|
||||
return s1_->Get(offset1_ + index1) == s2_->Get(offset2_ + index2);
|
||||
}
|
||||
|
||||
private:
|
||||
Handle<String> s1_;
|
||||
int offset1_;
|
||||
int len1_;
|
||||
Handle<String> s2_;
|
||||
int offset2_;
|
||||
int len2_;
|
||||
};
|
||||
|
||||
|
||||
// Stores compare result in JSArray. Converts substring positions
|
||||
// to absolute positions.
|
||||
class TokensCompareOutput : public Comparator::Output {
|
||||
public:
|
||||
TokensCompareOutput(CompareOutputArrayWriter* array_writer,
|
||||
int offset1, int offset2)
|
||||
: array_writer_(array_writer), offset1_(offset1), offset2_(offset2) {
|
||||
}
|
||||
|
||||
void AddChunk(int pos1, int pos2, int len1, int len2) {
|
||||
array_writer_->WriteChunk(pos1 + offset1_, pos2 + offset2_, len1, len2);
|
||||
}
|
||||
|
||||
private:
|
||||
CompareOutputArrayWriter* array_writer_;
|
||||
int offset1_;
|
||||
int offset2_;
|
||||
};
|
||||
|
||||
|
||||
// Wraps raw n-elements line_ends array as a list of n+1 lines. The last line
|
||||
// never has terminating new line character.
|
||||
class LineEndsWrapper {
|
||||
@ -350,13 +426,14 @@ class LineArrayCompareInput : public Comparator::Input {
|
||||
};
|
||||
|
||||
|
||||
// Stores compare result in JSArray. Each chunk is stored as 3 array elements:
|
||||
// (pos1_begin, pos1_end, pos2_end).
|
||||
class LineArrayCompareOutput : public Comparator::Output {
|
||||
// Stores compare result in JSArray. For each chunk tries to conduct
|
||||
// a fine-grained nested diff token-wise.
|
||||
class TokenizingLineArrayCompareOutput : public Comparator::Output {
|
||||
public:
|
||||
LineArrayCompareOutput(LineEndsWrapper line_ends1, LineEndsWrapper line_ends2)
|
||||
: array_(Factory::NewJSArray(10)), current_size_(0),
|
||||
line_ends1_(line_ends1), line_ends2_(line_ends2) {
|
||||
TokenizingLineArrayCompareOutput(LineEndsWrapper line_ends1,
|
||||
LineEndsWrapper line_ends2,
|
||||
Handle<String> s1, Handle<String> s2)
|
||||
: line_ends1_(line_ends1), line_ends2_(line_ends2), s1_(s1), s2_(s2) {
|
||||
}
|
||||
|
||||
void AddChunk(int line_pos1, int line_pos2, int line_len1, int line_len2) {
|
||||
@ -365,33 +442,43 @@ class LineArrayCompareOutput : public Comparator::Output {
|
||||
int char_len1 = line_ends1_.GetLineStart(line_pos1 + line_len1) - char_pos1;
|
||||
int char_len2 = line_ends2_.GetLineStart(line_pos2 + line_len2) - char_pos2;
|
||||
|
||||
SetElement(array_, current_size_, Handle<Object>(Smi::FromInt(char_pos1)));
|
||||
SetElement(array_, current_size_ + 1,
|
||||
Handle<Object>(Smi::FromInt(char_pos1 + char_len1)));
|
||||
SetElement(array_, current_size_ + 2,
|
||||
Handle<Object>(Smi::FromInt(char_pos2 + char_len2)));
|
||||
current_size_ += 3;
|
||||
if (char_len1 < CHUNK_LEN_LIMIT && char_len2 < CHUNK_LEN_LIMIT) {
|
||||
// Chunk is small enough to conduct a nested token-level diff.
|
||||
HandleScope subTaskScope;
|
||||
|
||||
TokensCompareInput tokens_input(s1_, char_pos1, char_len1,
|
||||
s2_, char_pos2, char_len2);
|
||||
TokensCompareOutput tokens_output(&array_writer_, char_pos1,
|
||||
char_pos2);
|
||||
|
||||
Comparator::CalculateDifference(&tokens_input, &tokens_output);
|
||||
} else {
|
||||
array_writer_.WriteChunk(char_pos1, char_pos2, char_len1, char_len2);
|
||||
}
|
||||
}
|
||||
|
||||
Handle<JSArray> GetResult() {
|
||||
return array_;
|
||||
return array_writer_.GetResult();
|
||||
}
|
||||
|
||||
private:
|
||||
Handle<JSArray> array_;
|
||||
int current_size_;
|
||||
static const int CHUNK_LEN_LIMIT = 800;
|
||||
|
||||
CompareOutputArrayWriter array_writer_;
|
||||
LineEndsWrapper line_ends1_;
|
||||
LineEndsWrapper line_ends2_;
|
||||
Handle<String> s1_;
|
||||
Handle<String> s2_;
|
||||
};
|
||||
|
||||
|
||||
Handle<JSArray> LiveEdit::CompareStringsLinewise(Handle<String> s1,
|
||||
Handle<String> s2) {
|
||||
Handle<JSArray> LiveEdit::CompareStrings(Handle<String> s1,
|
||||
Handle<String> s2) {
|
||||
LineEndsWrapper line_ends1(s1);
|
||||
LineEndsWrapper line_ends2(s2);
|
||||
|
||||
LineArrayCompareInput input(s1, s2, line_ends1, line_ends2);
|
||||
LineArrayCompareOutput output(line_ends1, line_ends2);
|
||||
TokenizingLineArrayCompareOutput output(line_ends1, line_ends2, s1, s2);
|
||||
|
||||
Comparator::CalculateDifference(&input, &output);
|
||||
|
||||
|
@ -126,10 +126,11 @@ class LiveEdit : AllStatic {
|
||||
FUNCTION_REPLACED_ON_ACTIVE_STACK = 5
|
||||
};
|
||||
|
||||
// Compares 2 strings line-by-line and returns diff in form of array of
|
||||
// triplets (pos1, pos1_end, pos2_end) describing list of diff chunks.
|
||||
static Handle<JSArray> CompareStringsLinewise(Handle<String> s1,
|
||||
Handle<String> s2);
|
||||
// Compares 2 strings line-by-line, then token-wise and returns diff in form
|
||||
// of array of triplets (pos1, pos1_end, pos2_end) describing list
|
||||
// of diff chunks.
|
||||
static Handle<JSArray> CompareStrings(Handle<String> s1,
|
||||
Handle<String> s2);
|
||||
};
|
||||
|
||||
|
||||
|
@ -10334,15 +10334,16 @@ static MaybeObject* Runtime_LiveEditCheckAndDropActivations(Arguments args) {
|
||||
return *LiveEdit::CheckAndDropActivations(shared_array, do_drop);
|
||||
}
|
||||
|
||||
// Compares 2 strings line-by-line and returns diff in form of JSArray of
|
||||
// triplets (pos1, pos1_end, pos2_end) describing list of diff chunks.
|
||||
static MaybeObject* Runtime_LiveEditCompareStringsLinewise(Arguments args) {
|
||||
// Compares 2 strings line-by-line, then token-wise and returns diff in form
|
||||
// of JSArray of triplets (pos1, pos1_end, pos2_end) describing list
|
||||
// of diff chunks.
|
||||
static MaybeObject* Runtime_LiveEditCompareStrings(Arguments args) {
|
||||
ASSERT(args.length() == 2);
|
||||
HandleScope scope;
|
||||
CONVERT_ARG_CHECKED(String, s1, 0);
|
||||
CONVERT_ARG_CHECKED(String, s2, 1);
|
||||
|
||||
return *LiveEdit::CompareStringsLinewise(s1, s2);
|
||||
return *LiveEdit::CompareStrings(s1, s2);
|
||||
}
|
||||
|
||||
|
||||
|
@ -361,7 +361,7 @@ namespace internal {
|
||||
F(LiveEditReplaceRefToNestedFunction, 3, 1) \
|
||||
F(LiveEditPatchFunctionPositions, 2, 1) \
|
||||
F(LiveEditCheckAndDropActivations, 2, 1) \
|
||||
F(LiveEditCompareStringsLinewise, 2, 1) \
|
||||
F(LiveEditCompareStrings, 2, 1) \
|
||||
F(GetFunctionCodePositionFromSource, 2, 1) \
|
||||
F(ExecuteInDebugContext, 2, 1) \
|
||||
\
|
||||
|
@ -31,11 +31,15 @@
|
||||
Debug = debug.Debug
|
||||
|
||||
function CheckCompareOneWay(s1, s2) {
|
||||
var diff_array = Debug.LiveEdit.TestApi.CompareStringsLinewise(s1, s2);
|
||||
var diff_array = Debug.LiveEdit.TestApi.CompareStrings(s1, s2);
|
||||
|
||||
var pos1 = 0;
|
||||
var pos2 = 0;
|
||||
print("Compare:");
|
||||
print("s1='" + s1 + "'");
|
||||
print("s2='" + s2 + "'");
|
||||
print("Diff:");
|
||||
print("" + diff_array);
|
||||
for (var i = 0; i < diff_array.length; i += 3) {
|
||||
var similar_length = diff_array[i] - pos1;
|
||||
assertEquals(s1.substring(pos1, pos1 + similar_length),
|
||||
@ -45,12 +49,12 @@ function CheckCompareOneWay(s1, s2) {
|
||||
pos1 += similar_length;
|
||||
pos2 += similar_length;
|
||||
print("<<< " + pos1 + " " + diff_array[i + 1]);
|
||||
print(s1.substring(pos1, pos1 + diff_array[i + 1]));
|
||||
print(s1.substring(pos1, diff_array[i + 1]));
|
||||
print("===");
|
||||
print(s2.substring(pos2, pos2 + diff_array[i + 2]));
|
||||
print(s2.substring(pos2, diff_array[i + 2]));
|
||||
print(">>> " + pos2 + " " + diff_array[i + 2]);
|
||||
pos1 += diff_array[i + 1];
|
||||
pos2 += diff_array[i + 2];
|
||||
pos1 = diff_array[i + 1];
|
||||
pos2 = diff_array[i + 2];
|
||||
}
|
||||
{
|
||||
// After last change
|
||||
@ -64,9 +68,18 @@ function CheckCompareOneWay(s1, s2) {
|
||||
print("");
|
||||
}
|
||||
|
||||
function CheckCompare(s1, s2) {
|
||||
function CheckCompareOneWayPlayWithLF(s1, s2) {
|
||||
var s1Oneliner = s1.replace(/\n/g, ' ');
|
||||
var s2Oneliner = s2.replace(/\n/g, ' ');
|
||||
CheckCompareOneWay(s1, s2);
|
||||
CheckCompareOneWay(s2, s1);
|
||||
CheckCompareOneWay(s1Oneliner, s2);
|
||||
CheckCompareOneWay(s1, s2Oneliner);
|
||||
CheckCompareOneWay(s1Oneliner, s2Oneliner);
|
||||
}
|
||||
|
||||
function CheckCompare(s1, s2) {
|
||||
CheckCompareOneWayPlayWithLF(s1, s2);
|
||||
CheckCompareOneWayPlayWithLF(s2, s1);
|
||||
}
|
||||
|
||||
CheckCompare("", "");
|
||||
|
@ -64,6 +64,6 @@ print("Change log: " + JSON.stringify(change_log) + "\n");
|
||||
assertEquals("Capybara", ChooseAnimal());
|
||||
// Global variable do not get changed (without restarting script).
|
||||
assertEquals(25, something1);
|
||||
// Function is oneliner, so currently it is treated as damaged and not patched.
|
||||
assertEquals(17, ChooseNumber());
|
||||
// We should support changes in oneliners.
|
||||
assertEquals(18, ChooseNumber());
|
||||
assertEquals("Hello Peter", ChooseAnimal.Factory()("Peter"));
|
||||
|
Loading…
Reference in New Issue
Block a user