summaryrefslogtreecommitdiffstats
path: root/acf/model/node.lua
diff options
context:
space:
mode:
Diffstat (limited to 'acf/model/node.lua')
-rw-r--r--acf/model/node.lua129
1 files changed, 129 insertions, 0 deletions
diff --git a/acf/model/node.lua b/acf/model/node.lua
new file mode 100644
index 0000000..39a0d92
--- /dev/null
+++ b/acf/model/node.lua
@@ -0,0 +1,129 @@
+--[[
+Copyright (c) 2012 Kaarle Ritvanen
+See LICENSE file for license details
+--]]
+
+module(..., package.seeall)
+
+local object = require('acf.object')
+local class = object.class
+local super = object.super
+
+local pth = require('acf.path')
+
+
+BoundField = class()
+
+function BoundField:init(parent, field)
+ local pmt = getmetatable(parent)
+ local mt = {}
+
+ function mt.__index(t, k)
+ local member = field[k]
+ if type(member) ~= 'function' then return member end
+ return function(self, ...)
+ local name
+ if field.name then name = field.name
+ else
+ name = arg[1]
+ table.remove(arg, 1)
+ end
+ return member(field,
+ pmt.txn,
+ pth.join(pmt.path, name),
+ pmt.addr and pth.join(pmt.addr, name),
+ unpack(arg))
+ end
+ end
+
+ setmetatable(self, mt)
+end
+
+
+TreeNode = class()
+
+function TreeNode:init(txn, path, addr)
+ local mt = getmetatable(self)
+ mt.txn = txn
+ mt.path = path
+ mt.addr = addr
+end
+
+function TreeNode:search(path)
+ if #path == 0 then return self end
+ local next = path[1]
+ table.remove(path, 1)
+ return TreeNode.search(self[next], path)
+end
+
+
+Collection = class(TreeNode)
+
+function Collection:init(txn, path, addr, field, required)
+ super(self, Collection):init(txn, path, addr)
+
+ if required then
+ txn.validate[path] = function()
+ if #txn:get(addr) == 0 then
+ error('Collection cannot be empty: '..path)
+ end
+ end
+ end
+
+ self.init = nil
+ self.search = nil
+
+ local mt = getmetatable(self)
+
+ mt.field = BoundField(self, field)
+ mt.meta = {type='collection', members=mt.field:meta()}
+ function mt.mmeta(name) return mt.meta.members end
+ function mt.members() return txn:get(addr) or {} end
+
+ function mt.__index(t, k) return mt.field:load(k) end
+ function mt.__newindex(t, k, v) mt.field:save(k, v) end
+end
+
+
+PrimitiveList = class(Collection)
+
+function PrimitiveList:init(txn, path, addr, field, required)
+ super(self, PrimitiveList):init(txn, path, addr, field, required)
+
+ local mt = getmetatable(self)
+ local index = mt.__index
+
+ function mt.__index(t, k)
+ if type(k) == 'number' then return index(t, k) end
+
+ for i, j in ipairs(txn:get(addr) or {}) do
+ assert(i == tonumber(j))
+ if mt.field:load(i) == k then return k end
+ end
+ error('Value does not exist: '..k)
+ end
+end
+
+
+-- experimental
+Mixed = class(Collection)
+
+function Mixed:init(txn, path, addr, field, required)
+ super(self, Mixed):init(txn, path, addr, field, required)
+ -- TODO dynamic meta: list non-leaf children
+ getmetatable(self).meta = {type='mixed'}
+end
+
+
+local function meta_func(attr)
+ return function(node, ...)
+ local res = getmetatable(node)[attr]
+ if type(res) == 'function' then return res(unpack(arg)) end
+ return res
+ end
+end
+
+members = meta_func('members')
+meta = meta_func('meta')
+mmeta = meta_func('mmeta')
+path = meta_func('path')