LiveEdit: calculate a real script difference
Review URL: http://codereview.chromium.org/1652008 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@4441 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
parent
07db17ce1b
commit
8fdc7a5ae1
@ -457,6 +457,16 @@ void InitScriptLineEnds(Handle<Script> script) {
|
||||
}
|
||||
|
||||
Handle<String> src(String::cast(script->source()));
|
||||
|
||||
Handle<FixedArray> array = CalculateLineEnds(src, true);
|
||||
|
||||
script->set_line_ends(*array);
|
||||
ASSERT(script->line_ends()->IsFixedArray());
|
||||
}
|
||||
|
||||
|
||||
Handle<FixedArray> CalculateLineEnds(Handle<String> src,
|
||||
bool with_imaginary_last_new_line) {
|
||||
const int src_len = src->length();
|
||||
Handle<String> new_line = Factory::NewStringFromAscii(CStrVector("\n"));
|
||||
|
||||
@ -468,8 +478,12 @@ void InitScriptLineEnds(Handle<Script> script) {
|
||||
if (position != -1) {
|
||||
position++;
|
||||
}
|
||||
// Even if the last line misses a line end, it is counted.
|
||||
line_count++;
|
||||
if (position != -1) {
|
||||
line_count++;
|
||||
} else if (with_imaginary_last_new_line) {
|
||||
// Even if the last line misses a line end, it is counted.
|
||||
line_count++;
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 2: Fill in line ends positions
|
||||
@ -478,15 +492,17 @@ void InitScriptLineEnds(Handle<Script> script) {
|
||||
position = 0;
|
||||
while (position != -1 && position < src_len) {
|
||||
position = Runtime::StringMatch(src, new_line, position);
|
||||
// If the script does not end with a line ending add the final end
|
||||
// position as just past the last line ending.
|
||||
array->set(array_index++,
|
||||
Smi::FromInt(position != -1 ? position++ : src_len));
|
||||
if (position != -1) {
|
||||
array->set(array_index++, Smi::FromInt(position++));
|
||||
} else if (with_imaginary_last_new_line) {
|
||||
// If the script does not end with a line ending add the final end
|
||||
// position as just past the last line ending.
|
||||
array->set(array_index++, Smi::FromInt(src_len));
|
||||
}
|
||||
}
|
||||
ASSERT(array_index == line_count);
|
||||
|
||||
script->set_line_ends(*array);
|
||||
ASSERT(script->line_ends()->IsFixedArray());
|
||||
return array;
|
||||
}
|
||||
|
||||
|
||||
|
@ -271,6 +271,11 @@ Handle<JSValue> GetScriptWrapper(Handle<Script> script);
|
||||
|
||||
// Script line number computations.
|
||||
void InitScriptLineEnds(Handle<Script> script);
|
||||
// For string calculates an array of line end positions. If the string
|
||||
// does not end with a new line character, this character may optionally be
|
||||
// imagined.
|
||||
Handle<FixedArray> CalculateLineEnds(Handle<String> string,
|
||||
bool with_imaginary_last_new_line);
|
||||
int GetScriptLineNumber(Handle<Script> script, int code_position);
|
||||
// The safe version does not make heap allocations but may work much slower.
|
||||
int GetScriptLineNumberSafe(Handle<Script> script, int code_position);
|
||||
|
@ -36,19 +36,19 @@ Debug.LiveEdit = new function() {
|
||||
// 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,
|
||||
// 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.
|
||||
//
|
||||
// @param {Script} script that is being changed
|
||||
// @param {Array} change_log a list that collects engineer-readable description
|
||||
// of what happened.
|
||||
// @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) {
|
||||
|
||||
|
||||
// Fully compiles source string as a script. Returns Array of
|
||||
// FunctionCompileInfo -- a descriptions of all functions of the script.
|
||||
// Elements of array are ordered by start positions of functions (from top
|
||||
@ -58,8 +58,8 @@ 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) {
|
||||
// Get function info, elements are partially sorted (it is a tree
|
||||
// of nested functions serialized as parent followed by serialized children.
|
||||
// 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);
|
||||
|
||||
// Sort function infos by start position field.
|
||||
@ -117,7 +117,8 @@ Debug.LiveEdit = new function() {
|
||||
return compile_info;
|
||||
}
|
||||
|
||||
// Given a positions, finds a function that fully includes the entire change.
|
||||
// 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
|
||||
@ -269,7 +270,8 @@ Debug.LiveEdit = new function() {
|
||||
|
||||
// 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 old_script = %LiveEditReplaceScript(script, new_source,
|
||||
old_script_name);
|
||||
|
||||
PatchCode(new_compile_info[function_being_patched],
|
||||
FindFunctionInfo(function_being_patched));
|
||||
@ -477,6 +479,12 @@ Debug.LiveEdit = new function() {
|
||||
}
|
||||
// Function is public.
|
||||
this.SetScriptSource = SetScriptSource;
|
||||
|
||||
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.
|
||||
|
352
src/liveedit.cc
352
src/liveedit.cc
@ -42,6 +42,358 @@ namespace internal {
|
||||
|
||||
#ifdef ENABLE_DEBUGGER_SUPPORT
|
||||
|
||||
|
||||
// A simple implementation of dynamic programming algorithm. It solves
|
||||
// the problem of finding the difference of 2 arrays. It uses a table of results
|
||||
// of subproblems. Each cell contains a number together with 2-bit flag
|
||||
// that helps building the chunk list.
|
||||
class Differencer {
|
||||
public:
|
||||
explicit Differencer(Compare::Input* input)
|
||||
: input_(input), len1_(input->getLength1()), len2_(input->getLength2()) {
|
||||
buffer_ = NewArray<int>(len1_ * len2_);
|
||||
}
|
||||
~Differencer() {
|
||||
DeleteArray(buffer_);
|
||||
}
|
||||
|
||||
void Initialize() {
|
||||
int array_size = len1_ * len2_;
|
||||
for (int i = 0; i < array_size; i++) {
|
||||
buffer_[i] = kEmptyCellValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Makes sure that result for the full problem is calculated and stored
|
||||
// in the table together with flags showing a path through subproblems.
|
||||
void FillTable() {
|
||||
CompareUpToTail(0, 0);
|
||||
}
|
||||
|
||||
void SaveResult(Compare::Output* chunk_writer) {
|
||||
ResultWriter writer(chunk_writer);
|
||||
|
||||
int pos1 = 0;
|
||||
int pos2 = 0;
|
||||
while (true) {
|
||||
if (pos1 < len1_) {
|
||||
if (pos2 < len2_) {
|
||||
Direction dir = get_direction(pos1, pos2);
|
||||
switch (dir) {
|
||||
case EQ:
|
||||
writer.eq();
|
||||
pos1++;
|
||||
pos2++;
|
||||
break;
|
||||
case SKIP1:
|
||||
writer.skip1(1);
|
||||
pos1++;
|
||||
break;
|
||||
case SKIP2:
|
||||
case SKIP_ANY:
|
||||
writer.skip2(1);
|
||||
pos2++;
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
} else {
|
||||
writer.skip1(len1_ - pos1);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (len2_ != pos2) {
|
||||
writer.skip2(len2_ - pos2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
writer.close();
|
||||
}
|
||||
|
||||
private:
|
||||
Compare::Input* input_;
|
||||
int* buffer_;
|
||||
int len1_;
|
||||
int len2_;
|
||||
|
||||
enum Direction {
|
||||
EQ = 0,
|
||||
SKIP1,
|
||||
SKIP2,
|
||||
SKIP_ANY,
|
||||
|
||||
MAX_DIRECTION_FLAG_VALUE = SKIP_ANY
|
||||
};
|
||||
|
||||
// Computes result for a subtask and optionally caches it in the buffer table.
|
||||
// All results values are shifted to make space for flags in the lower bits.
|
||||
int CompareUpToTail(int pos1, int pos2) {
|
||||
if (pos1 < len1_) {
|
||||
if (pos2 < len2_) {
|
||||
int cached_res = get_value4(pos1, pos2);
|
||||
if (cached_res == kEmptyCellValue) {
|
||||
Direction dir;
|
||||
int res;
|
||||
if (input_->equals(pos1, pos2)) {
|
||||
res = CompareUpToTail(pos1 + 1, pos2 + 1);
|
||||
dir = EQ;
|
||||
} else {
|
||||
int res1 = CompareUpToTail(pos1 + 1, pos2) +
|
||||
(1 << kDirectionSizeBits);
|
||||
int res2 = CompareUpToTail(pos1, pos2 + 1) +
|
||||
(1 << kDirectionSizeBits);
|
||||
if (res1 == res2) {
|
||||
res = res1;
|
||||
dir = SKIP_ANY;
|
||||
} else if (res1 < res2) {
|
||||
res = res1;
|
||||
dir = SKIP1;
|
||||
} else {
|
||||
res = res2;
|
||||
dir = SKIP2;
|
||||
}
|
||||
}
|
||||
set_value4_and_dir(pos1, pos2, res, dir);
|
||||
cached_res = res;
|
||||
}
|
||||
return cached_res;
|
||||
} else {
|
||||
return (len1_ - pos1) << kDirectionSizeBits;
|
||||
}
|
||||
} else {
|
||||
return (len2_ - pos2) << kDirectionSizeBits;
|
||||
}
|
||||
}
|
||||
|
||||
inline int& get_cell(int i1, int i2) {
|
||||
return buffer_[i1 + i2 * len1_];
|
||||
}
|
||||
|
||||
// Each cell keeps a value plus direction. Value is multiplied by 4.
|
||||
void set_value4_and_dir(int i1, int i2, int value4, Direction dir) {
|
||||
ASSERT((value4 & kDirectionMask) == 0);
|
||||
get_cell(i1, i2) = value4 | dir;
|
||||
}
|
||||
|
||||
int get_value4(int i1, int i2) {
|
||||
return get_cell(i1, i2) & (kMaxUInt32 ^ kDirectionMask);
|
||||
}
|
||||
Direction get_direction(int i1, int i2) {
|
||||
return static_cast<Direction>(get_cell(i1, i2) & kDirectionMask);
|
||||
}
|
||||
|
||||
static const int kDirectionSizeBits = 2;
|
||||
static const int kDirectionMask = (1 << kDirectionSizeBits) - 1;
|
||||
static const int kEmptyCellValue = -1 << kDirectionSizeBits;
|
||||
|
||||
// This method only holds static assert statement (unfortunately you cannot
|
||||
// place one in class scope).
|
||||
void StaticAssertHolder() {
|
||||
STATIC_ASSERT(MAX_DIRECTION_FLAG_VALUE < (1 << kDirectionSizeBits));
|
||||
}
|
||||
|
||||
class ResultWriter {
|
||||
public:
|
||||
explicit ResultWriter(Compare::Output* chunk_writer)
|
||||
: chunk_writer_(chunk_writer), pos1_(0), pos2_(0),
|
||||
pos1_begin_(-1), pos2_begin_(-1), has_open_chunk_(false) {
|
||||
}
|
||||
void eq() {
|
||||
FlushChunk();
|
||||
pos1_++;
|
||||
pos2_++;
|
||||
}
|
||||
void skip1(int len1) {
|
||||
StartChunk();
|
||||
pos1_ += len1;
|
||||
}
|
||||
void skip2(int len2) {
|
||||
StartChunk();
|
||||
pos2_ += len2;
|
||||
}
|
||||
void close() {
|
||||
FlushChunk();
|
||||
}
|
||||
|
||||
private:
|
||||
Compare::Output* chunk_writer_;
|
||||
int pos1_;
|
||||
int pos2_;
|
||||
int pos1_begin_;
|
||||
int pos2_begin_;
|
||||
bool has_open_chunk_;
|
||||
|
||||
void StartChunk() {
|
||||
if (!has_open_chunk_) {
|
||||
pos1_begin_ = pos1_;
|
||||
pos2_begin_ = pos2_;
|
||||
has_open_chunk_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void FlushChunk() {
|
||||
if (has_open_chunk_) {
|
||||
chunk_writer_->AddChunk(pos1_begin_, pos2_begin_,
|
||||
pos1_ - pos1_begin_, pos2_ - pos2_begin_);
|
||||
has_open_chunk_ = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
void Compare::CalculateDifference(Compare::Input* input,
|
||||
Compare::Output* result_writer) {
|
||||
Differencer differencer(input);
|
||||
differencer.Initialize();
|
||||
differencer.FillTable();
|
||||
differencer.SaveResult(result_writer);
|
||||
}
|
||||
|
||||
|
||||
static bool CompareSubstrings(Handle<String> s1, int pos1,
|
||||
Handle<String> s2, int pos2, int len) {
|
||||
static StringInputBuffer buf1;
|
||||
static StringInputBuffer buf2;
|
||||
buf1.Reset(*s1);
|
||||
buf1.Seek(pos1);
|
||||
buf2.Reset(*s2);
|
||||
buf2.Seek(pos2);
|
||||
for (int i = 0; i < len; i++) {
|
||||
ASSERT(buf1.has_more() && buf2.has_more());
|
||||
if (buf1.GetNext() != buf2.GetNext()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// 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 {
|
||||
public:
|
||||
explicit LineEndsWrapper(Handle<String> string)
|
||||
: ends_array_(CalculateLineEnds(string, false)),
|
||||
string_len_(string->length()) {
|
||||
}
|
||||
int length() {
|
||||
return ends_array_->length() + 1;
|
||||
}
|
||||
// Returns start for any line including start of the imaginary line after
|
||||
// the last line.
|
||||
int GetLineStart(int index) {
|
||||
if (index == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return GetLineEnd(index - 1);
|
||||
}
|
||||
}
|
||||
int GetLineEnd(int index) {
|
||||
if (index == ends_array_->length()) {
|
||||
// End of the last line is always an end of the whole string.
|
||||
// If the string ends with a new line character, the last line is an
|
||||
// empty string after this character.
|
||||
return string_len_;
|
||||
} else {
|
||||
return GetPosAfterNewLine(index);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Handle<FixedArray> ends_array_;
|
||||
int string_len_;
|
||||
|
||||
int GetPosAfterNewLine(int index) {
|
||||
return Smi::cast(ends_array_->get(index))->value() + 1;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Represents 2 strings as 2 arrays of lines.
|
||||
class LineArrayCompareInput : public Compare::Input {
|
||||
public:
|
||||
LineArrayCompareInput(Handle<String> s1, Handle<String> s2,
|
||||
LineEndsWrapper line_ends1, LineEndsWrapper line_ends2)
|
||||
: s1_(s1), s2_(s2), line_ends1_(line_ends1), line_ends2_(line_ends2) {
|
||||
}
|
||||
int getLength1() {
|
||||
return line_ends1_.length();
|
||||
}
|
||||
int getLength2() {
|
||||
return line_ends2_.length();
|
||||
}
|
||||
bool equals(int index1, int index2) {
|
||||
int line_start1 = line_ends1_.GetLineStart(index1);
|
||||
int line_start2 = line_ends2_.GetLineStart(index2);
|
||||
int line_end1 = line_ends1_.GetLineEnd(index1);
|
||||
int line_end2 = line_ends2_.GetLineEnd(index2);
|
||||
int len1 = line_end1 - line_start1;
|
||||
int len2 = line_end2 - line_start2;
|
||||
if (len1 != len2) {
|
||||
return false;
|
||||
}
|
||||
return CompareSubstrings(s1_, line_start1, s2_, line_start2, len1);
|
||||
}
|
||||
|
||||
private:
|
||||
Handle<String> s1_;
|
||||
Handle<String> s2_;
|
||||
LineEndsWrapper line_ends1_;
|
||||
LineEndsWrapper line_ends2_;
|
||||
};
|
||||
|
||||
|
||||
// Stores compare result in JSArray. Each chunk is stored as 3 array elements:
|
||||
// (pos1, len1, len2).
|
||||
class LineArrayCompareOutput : public Compare::Output {
|
||||
public:
|
||||
LineArrayCompareOutput(LineEndsWrapper line_ends1, LineEndsWrapper line_ends2)
|
||||
: array_(Factory::NewJSArray(10)), current_size_(0),
|
||||
line_ends1_(line_ends1), line_ends2_(line_ends2) {
|
||||
}
|
||||
|
||||
void AddChunk(int line_pos1, int line_pos2, int line_len1, int line_len2) {
|
||||
int char_pos1 = line_ends1_.GetLineStart(line_pos1);
|
||||
int char_pos2 = line_ends2_.GetLineStart(line_pos2);
|
||||
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_len1)));
|
||||
SetElement(array_, current_size_ + 2,
|
||||
Handle<Object>(Smi::FromInt(char_len2)));
|
||||
current_size_ += 3;
|
||||
}
|
||||
|
||||
Handle<JSArray> GetResult() {
|
||||
return array_;
|
||||
}
|
||||
|
||||
private:
|
||||
Handle<JSArray> array_;
|
||||
int current_size_;
|
||||
LineEndsWrapper line_ends1_;
|
||||
LineEndsWrapper line_ends2_;
|
||||
};
|
||||
|
||||
|
||||
Handle<JSArray> LiveEdit::CompareStringsLinewise(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);
|
||||
|
||||
Compare::CalculateDifference(&input, &output);
|
||||
|
||||
return output.GetResult();
|
||||
}
|
||||
|
||||
|
||||
static void CompileScriptForTracker(Handle<Script> script) {
|
||||
const bool is_eval = false;
|
||||
const bool is_global = true;
|
||||
|
@ -109,6 +109,44 @@ class LiveEdit : AllStatic {
|
||||
FUNCTION_BLOCKED_UNDER_NATIVE_CODE = 4,
|
||||
FUNCTION_REPLACED_ON_ACTIVE_STACK = 5
|
||||
};
|
||||
|
||||
// Compares 2 strings line-by-line and returns diff in form of array of
|
||||
// triplets (pos1, len1, len2) describing list of diff chunks.
|
||||
static Handle<JSArray> CompareStringsLinewise(Handle<String> s1,
|
||||
Handle<String> s2);
|
||||
};
|
||||
|
||||
|
||||
// A general-purpose comparator between 2 arrays.
|
||||
class Compare {
|
||||
public:
|
||||
|
||||
// Holds 2 arrays of some elements allowing to compare any pair of
|
||||
// element from the first array and element from the second array.
|
||||
class Input {
|
||||
public:
|
||||
virtual int getLength1() = 0;
|
||||
virtual int getLength2() = 0;
|
||||
virtual bool equals(int index1, int index2) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~Input() {}
|
||||
};
|
||||
|
||||
// Receives compare result as a series of chunks.
|
||||
class Output {
|
||||
public:
|
||||
// Puts another chunk in result list. Note that technically speaking
|
||||
// only 3 arguments actually needed with 4th being derivable.
|
||||
virtual void AddChunk(int pos1, int pos2, int len1, int len2) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~Output() {}
|
||||
};
|
||||
|
||||
// Finds the difference between 2 arrays of elements.
|
||||
static void CalculateDifference(Input* input,
|
||||
Output* result_writer);
|
||||
};
|
||||
|
||||
#endif // ENABLE_DEBUGGER_SUPPORT
|
||||
|
@ -9756,10 +9756,21 @@ static Object* Runtime_LiveEditCheckAndDropActivations(Arguments args) {
|
||||
CONVERT_ARG_CHECKED(JSArray, shared_array, 0);
|
||||
CONVERT_BOOLEAN_CHECKED(do_drop, args[1]);
|
||||
|
||||
|
||||
return *LiveEdit::CheckAndDropActivations(shared_array, do_drop);
|
||||
}
|
||||
|
||||
// Compares 2 strings line-by-line and returns diff in form of JSArray of
|
||||
// triplets (pos1, len1, len2) describing list of diff chunks.
|
||||
static Object* Runtime_LiveEditCompareStringsLinewise(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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// A testing entry. Returns statement position which is the closest to
|
||||
// source_position.
|
||||
|
@ -340,6 +340,7 @@ namespace internal {
|
||||
F(LiveEditRelinkFunctionToScript, 2, 1) \
|
||||
F(LiveEditPatchFunctionPositions, 2, 1) \
|
||||
F(LiveEditCheckAndDropActivations, 2, 1) \
|
||||
F(LiveEditCompareStringsLinewise, 2, 1) \
|
||||
F(GetFunctionCodePositionFromSource, 2, 1) \
|
||||
F(ExecuteInDebugContext, 2, 1)
|
||||
#else
|
||||
|
@ -55,6 +55,7 @@ SOURCES = {
|
||||
'test-heap.cc',
|
||||
'test-heap-profiler.cc',
|
||||
'test-list.cc',
|
||||
'test-liveedit.cc',
|
||||
'test-lock.cc',
|
||||
'test-log.cc',
|
||||
'test-log-utils.cc',
|
||||
|
174
test/cctest/test-liveedit.cc
Normal file
174
test/cctest/test-liveedit.cc
Normal file
@ -0,0 +1,174 @@
|
||||
// Copyright 2007-2008 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "v8.h"
|
||||
|
||||
#include "liveedit.h"
|
||||
#include "cctest.h"
|
||||
|
||||
|
||||
using namespace v8::internal;
|
||||
|
||||
// Anonymous namespace.
|
||||
namespace {
|
||||
|
||||
class StringCompareInput : public Compare::Input {
|
||||
public:
|
||||
StringCompareInput(const char* s1, const char* s2) : s1_(s1), s2_(s2) {
|
||||
}
|
||||
int getLength1() {
|
||||
return strlen(s1_);
|
||||
}
|
||||
int getLength2() {
|
||||
return strlen(s2_);
|
||||
}
|
||||
bool equals(int index1, int index2) {
|
||||
return s1_[index1] == s2_[index2];
|
||||
}
|
||||
|
||||
private:
|
||||
const char* s1_;
|
||||
const char* s2_;
|
||||
};
|
||||
|
||||
|
||||
class DiffChunkStruct : public ZoneObject {
|
||||
public:
|
||||
DiffChunkStruct(int pos1_param, int pos2_param,
|
||||
int len1_param, int len2_param)
|
||||
: pos1(pos1_param), pos2(pos2_param),
|
||||
len1(len1_param), len2(len2_param), next(NULL) {}
|
||||
int pos1;
|
||||
int pos2;
|
||||
int len1;
|
||||
int len2;
|
||||
DiffChunkStruct* next;
|
||||
};
|
||||
|
||||
|
||||
class ListDiffOutputWriter : public Compare::Output {
|
||||
public:
|
||||
explicit ListDiffOutputWriter(DiffChunkStruct** next_chunk_pointer)
|
||||
: next_chunk_pointer_(next_chunk_pointer) {
|
||||
(*next_chunk_pointer_) = NULL;
|
||||
}
|
||||
void AddChunk(int pos1, int pos2, int len1, int len2) {
|
||||
current_chunk_ = new DiffChunkStruct(pos1, pos2, len1, len2);
|
||||
(*next_chunk_pointer_) = current_chunk_;
|
||||
next_chunk_pointer_ = ¤t_chunk_->next;
|
||||
}
|
||||
private:
|
||||
DiffChunkStruct** next_chunk_pointer_;
|
||||
DiffChunkStruct* current_chunk_;
|
||||
};
|
||||
|
||||
|
||||
void CompareStringsOneWay(const char* s1, const char* s2,
|
||||
int expected_diff_parameter = -1) {
|
||||
StringCompareInput input(s1, s2);
|
||||
|
||||
ZoneScope zone_scope(DELETE_ON_EXIT);
|
||||
|
||||
DiffChunkStruct* first_chunk;
|
||||
ListDiffOutputWriter writer(&first_chunk);
|
||||
|
||||
Compare::CalculateDifference(&input, &writer);
|
||||
|
||||
int len1 = strlen(s1);
|
||||
int len2 = strlen(s2);
|
||||
|
||||
int pos1 = 0;
|
||||
int pos2 = 0;
|
||||
|
||||
int diff_parameter = 0;
|
||||
|
||||
for (DiffChunkStruct* chunk = first_chunk;
|
||||
chunk != NULL;
|
||||
chunk = chunk->next) {
|
||||
int diff_pos1 = chunk->pos1;
|
||||
int similar_part_length = diff_pos1 - pos1;
|
||||
int diff_pos2 = pos2 + similar_part_length;
|
||||
|
||||
ASSERT_EQ(diff_pos2, chunk->pos2);
|
||||
|
||||
for (int j = 0; j < similar_part_length; j++) {
|
||||
ASSERT(pos1 + j < len1);
|
||||
ASSERT(pos2 + j < len2);
|
||||
ASSERT_EQ(s1[pos1 + j], s2[pos2 + j]);
|
||||
}
|
||||
diff_parameter += chunk->len1 + chunk->len2;
|
||||
pos1 = diff_pos1 + chunk->len1;
|
||||
pos2 = diff_pos2 + chunk->len2;
|
||||
}
|
||||
{
|
||||
// After last chunk.
|
||||
int similar_part_length = len1 - pos1;
|
||||
ASSERT_EQ(similar_part_length, len2 - pos2);
|
||||
USE(len2);
|
||||
for (int j = 0; j < similar_part_length; j++) {
|
||||
ASSERT(pos1 + j < len1);
|
||||
ASSERT(pos2 + j < len2);
|
||||
ASSERT_EQ(s1[pos1 + j], s2[pos2 + j]);
|
||||
}
|
||||
}
|
||||
|
||||
if (expected_diff_parameter != -1) {
|
||||
ASSERT_EQ(expected_diff_parameter, diff_parameter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CompareStrings(const char* s1, const char* s2,
|
||||
int expected_diff_parameter = -1) {
|
||||
CompareStringsOneWay(s1, s2, expected_diff_parameter);
|
||||
CompareStringsOneWay(s2, s1, expected_diff_parameter);
|
||||
}
|
||||
|
||||
} // Anonymous namespace.
|
||||
|
||||
|
||||
// --- T h e A c t u a l T e s t s
|
||||
|
||||
TEST(LiveEditDiffer) {
|
||||
CompareStrings("zz1zzz12zz123zzz", "zzzzzzzzzz", 6);
|
||||
CompareStrings("zz1zzz12zz123zzz", "zz0zzz0zz0zzz", 9);
|
||||
CompareStrings("123456789", "987654321", 16);
|
||||
CompareStrings("zzz", "yyy", 6);
|
||||
CompareStrings("zzz", "zzz12", 2);
|
||||
CompareStrings("zzz", "21zzz", 2);
|
||||
CompareStrings("cat", "cut", 2);
|
||||
CompareStrings("ct", "cut", 1);
|
||||
CompareStrings("cat", "ct", 1);
|
||||
CompareStrings("cat", "cat", 0);
|
||||
CompareStrings("", "", 0);
|
||||
CompareStrings("cat", "", 3);
|
||||
CompareStrings("a cat", "a capybara", 7);
|
||||
CompareStrings("abbabababababaaabbabababababbabbbbbbbababa",
|
||||
"bbbbabababbbabababbbabababababbabbababa");
|
||||
}
|
100
test/mjsunit/debug-liveedit-diff.js
Normal file
100
test/mjsunit/debug-liveedit-diff.js
Normal file
@ -0,0 +1,100 @@
|
||||
// Copyright 2010 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Flags: --expose-debug-as debug
|
||||
// Get the Debug object exposed from the debug context global object.
|
||||
|
||||
Debug = debug.Debug
|
||||
|
||||
function CheckCompareOneWay(s1, s2) {
|
||||
var diff_array = Debug.LiveEdit.CompareStringsLinewise(s1, s2);
|
||||
|
||||
var pos1 = 0;
|
||||
var pos2 = 0;
|
||||
print("Compare:");
|
||||
for (var i = 0; i < diff_array.length; i += 3) {
|
||||
var similar_length = diff_array[i] - pos1;
|
||||
assertEquals(s1.substring(pos1, pos1 + similar_length),
|
||||
s2.substring(pos2, pos2 + similar_length));
|
||||
|
||||
print(s1.substring(pos1, pos1 + similar_length));
|
||||
pos1 += similar_length;
|
||||
pos2 += similar_length;
|
||||
print("<<< " + pos1 + " " + diff_array[i + 1]);
|
||||
print(s1.substring(pos1, pos1 + diff_array[i + 1]));
|
||||
print("===");
|
||||
print(s2.substring(pos2, pos2 + diff_array[i + 2]));
|
||||
print(">>> " + pos2 + " " + diff_array[i + 2]);
|
||||
pos1 += diff_array[i + 1];
|
||||
pos2 += diff_array[i + 2];
|
||||
}
|
||||
{
|
||||
// After last change
|
||||
var similar_length = s1.length - pos1;
|
||||
assertEquals(similar_length, s2.length - pos2);
|
||||
assertEquals(s1.substring(pos1, pos1 + similar_length),
|
||||
s2.substring(pos2, pos2 + similar_length));
|
||||
|
||||
print(s1.substring(pos1, pos1 + similar_length));
|
||||
}
|
||||
print("");
|
||||
}
|
||||
|
||||
function CheckCompare(s1, s2) {
|
||||
CheckCompareOneWay(s1, s2);
|
||||
CheckCompareOneWay(s2, s1);
|
||||
}
|
||||
|
||||
CheckCompare("", "");
|
||||
|
||||
CheckCompare("a", "b");
|
||||
|
||||
CheckCompare(
|
||||
"yesterday\nall\nmy\ntroubles\nseemed\nso\nfar\naway",
|
||||
"yesterday\nall\nmy\ntroubles\nseem\nso\nfar\naway"
|
||||
);
|
||||
|
||||
CheckCompare(
|
||||
"yesterday\nall\nmy\ntroubles\nseemed\nso\nfar\naway",
|
||||
"\nall\nmy\ntroubles\nseemed\nso\nfar\naway"
|
||||
);
|
||||
|
||||
CheckCompare(
|
||||
"yesterday\nall\nmy\ntroubles\nseemed\nso\nfar\naway",
|
||||
"all\nmy\ntroubles\nseemed\nso\nfar\naway"
|
||||
);
|
||||
|
||||
CheckCompare(
|
||||
"yesterday\nall\nmy\ntroubles\nseemed\nso\nfar\naway",
|
||||
"yesterday\nall\nmy\ntroubles\nseemed\nso\nfar\naway\n"
|
||||
);
|
||||
|
||||
CheckCompare(
|
||||
"yesterday\nall\nmy\ntroubles\nseemed\nso\nfar\naway",
|
||||
"yesterday\nall\nmy\ntroubles\nseemed\nso\n"
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user