}
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"));
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
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;
}
// 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);
// 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
// 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.
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
// 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));
}
// 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.
#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;
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
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.
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
'test-heap.cc',
'test-heap-profiler.cc',
'test-list.cc',
+ 'test-liveedit.cc',
'test-lock.cc',
'test-log.cc',
'test-log-utils.cc',
--- /dev/null
+// 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");
+}
--- /dev/null
+// 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"
+);
+