diff --git a/tracedoc.lua b/tracedoc.lua index 9bc254e..b45ad87 100644 --- a/tracedoc.lua +++ b/tracedoc.lua @@ -1,5 +1,6 @@ local next = next local pairs = pairs +local ipairs = ipairs local setmetatable = setmetatable local getmetatable = getmetatable local type = type @@ -10,93 +11,17 @@ local tracedoc = {} local NULL = setmetatable({} , { __tostring = function() return "NULL" end }) -- nil tracedoc.null = NULL local tracedoc_type = setmetatable({}, { __tostring = function() return "TRACEDOC" end }) -local tracedoc_len = setmetatable({} , { __mode = "kv" }) local function doc_next(doc, key) - -- at first, iterate all the keys changed - local change_keys = doc._keys - if key == nil or change_keys[key] then - while true do - key = next(change_keys, key) - if key == nil then - break - end - local v = doc[key] - if v ~= nil then - return key, v - end - end - end - - -- and then, iterate all the keys in lastversion except keys changed - - local lastversion = doc._lastversion - - while true do - key = next(lastversion, key) - if key == nil then - return - end - if not change_keys[key] then - local v = doc[key] - if v ~= nil then - return key, v - end - end - end + return next(doc._stage, key) end local function doc_pairs(doc) - return doc_next, doc -end - -local function find_length_after(doc, idx) - local v = doc[idx + 1] - if v == nil then - return idx - end - repeat - idx = idx + 1 - v = doc[idx + 1] - until v == nil - tracedoc_len[doc] = idx - return idx -end - -local function find_length_before(doc, idx) - if idx <= 1 then - tracedoc_len[doc] = nil - return 0 - end - repeat - idx = idx - 1 - until idx <=0 or doc[idx] ~= nil - tracedoc_len[doc] = idx - return idx + return pairs(doc._stage) end local function doc_len(doc) - local len = tracedoc_len[doc] - if len == nil then - len = #doc._lastversion - tracedoc_len[doc] = len - end - if len == 0 then - return find_length_after(doc, 0) - end - local v = doc[len] - if v == nil then - return find_length_before(doc, len) - end - return find_length_after(doc, len) -end - -local function doc_read(doc, k) - if doc._keys[k] then - return doc._changes[k] - end - -- if k is not changed, return lastversion - return doc._lastversion[k] + return #doc._stage end local function doc_change(doc, k, v) @@ -114,17 +39,20 @@ local function doc_change(doc, k, v) if type(v) == "table" then local vt = getmetatable(v) if vt == nil then - local lv = doc._lastversion[k] + local lv = doc._stage[k] if getmetatable(lv) ~= tracedoc_type then - -- last version is not a table, new a empty one - lv = tracedoc.new() - lv._parent = doc - doc._lastversion[k] = lv - elseif doc[k] == nil then - -- this version is clear first, deepcopy lastversion one - lv = tracedoc.new(lv) - lv._parent = doc - doc._lastversion[k] = lv + lv = doc._changed_values[k] + if getmetatable(lv) ~= tracedoc_type then + -- last version is not a table, new a empty one + lv = tracedoc.new() + lv._parent = doc + doc._stage[k] = lv + else + -- this version is clear first, deepcopy lastversion one + lv = tracedoc.new(lv) + lv._parent = doc + doc._stage[k] = lv + end end local keys = {} for k in pairs(lv) do @@ -139,33 +67,33 @@ local function doc_change(doc, k, v) for k in pairs(keys) do lv[k] = nil end - -- don't cache sub table into doc._changes - doc._changes[k] = nil - doc._keys[k] = nil + -- don't cache sub table into doc._changed_values + doc._changed_values[k] = nil + doc._changed_keys[k] = nil return end end - doc._changes[k] = v - doc._keys[k] = true + doc._changed_values[k] = doc._stage[k] + doc._changed_keys[k] = true + doc._stage[k] = v end -local doc_mt = { - __newindex = doc_change, - __index = doc_read, - __pairs = doc_pairs, - __len = doc_len, - __metatable = tracedoc_type, -- avoid copy by ref -} - function tracedoc.new(init) + local doc_stage = {} local doc = { _dirty = false, _parent = false, - _changes = {}, - _keys = {}, - _lastversion = {}, + _changed_keys = {}, + _changed_values = {}, + _stage = doc_stage, } - setmetatable(doc, doc_mt) + setmetatable(doc, { + __newindex = doc_change, + __index = doc_stage, + __pairs = doc_pairs, + __len = doc_len, + __metatable = tracedoc_type, -- avoid copy by ref + }) if init then for k,v in pairs(init) do -- deepcopy v @@ -180,19 +108,15 @@ function tracedoc.new(init) end function tracedoc.dump(doc) - local last = {} - for k,v in pairs(doc._lastversion) do - table.insert(last, string.format("%s:%s",k,v)) - end - local changes = {} - for k,v in pairs(doc._changes) do - table.insert(changes, string.format("%s:%s",k,v)) + local stage = {} + for k,v in pairs(doc._stage) do + table.insert(stage, string.format("%s:%s",k,v)) end - local keys = {} - for k in pairs(doc._keys) do - table.insert(keys, k) + local changed = {} + for k in pairs(doc._changed_keys) do + table.insert(changed, string.format("%s:%s",k,doc._changed_values[k])) end - return string.format("last [%s]\nchanges [%s]\nkeys [%s]",table.concat(last, " "), table.concat(changes," "), table.concat(keys," ")) + return string.format("content [%s]\nchanged [%s]",table.concat(stage, " "), table.concat(changed," ")) end function tracedoc.commit(doc, result, prefix) @@ -200,27 +124,26 @@ function tracedoc.commit(doc, result, prefix) return result end doc._dirty = false - local lastversion = doc._lastversion - local changes = doc._changes - local keys = doc._keys + local stage = doc._stage + local changed_values = doc._changed_values + local changed_keys = doc._changed_keys local dirty = false - if next(keys) ~= nil then - for k in next, keys do - local v = changes[k] - keys[k] = nil - changes[k] = nil - if lastversion[k] ~= v then - dirty = true - if result then - local key = prefix and prefix .. k or k - result[key] = v == nil and NULL or v - result._n = (result._n or 0) + 1 - end - lastversion[k] = v + for k in pairs(changed_keys) do + local v = stage[k] + local lv = changed_values[k] + changed_keys[k] = nil + changed_values[k] = nil + + if lv ~= v then + dirty = true + if result then + local key = prefix and prefix .. k or k + result[key] = v == nil and NULL or v + result._n = (result._n or 0) + 1 end end end - for k,v in pairs(lastversion) do + for k,v in pairs(stage) do if getmetatable(v) == tracedoc_type and v._dirty then if result then local key = prefix and prefix .. k or k