| Index: src/liveedit.cc
|
| diff --git a/src/liveedit.cc b/src/liveedit.cc
|
| index 8c1316b8490af599fbeff12d8529a82d17c0e87c..4b30b2a0659e3f556866710e6610d85a88304353 100644
|
| --- a/src/liveedit.cc
|
| +++ b/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;
|
|
|