summaryrefslogtreecommitdiffstats
path: root/acf/model/node.lua
blob: 083757c714b4cc8aba217218217900e2d7a74c36 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
--[[
Copyright (c) 2012-2013 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')