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:
peter.rybin@gmail.com 2010-04-19 16:08:26 +00:00
parent 07db17ce1b
commit 8fdc7a5ae1
10 changed files with 725 additions and 19 deletions

View File

@ -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;
}

View File

@ -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);

View File

@ -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.

View File

@ -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;

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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',

View 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_ = &current_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");
}

View 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"
);