summaryrefslogtreecommitdiffstats
path: root/acf/model/node.lua
blob: 3459fce50857662c6447bccbf45e094ce3d67108 (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
130
131
132
133
134
--[[
Copyright (c) 2012-2013 Kaarle Ritvanen
See LICENSE file for license details
--]]

module(..., package.seeall)

local raise = require('acf.error').raise
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 name = path[1]
   local next = self[name]
   if next == nil then
      raise(getmetatable(self).path, 'Subordinate does not exist: '..name)
   end
   table.remove(path, 1)
   return TreeNode.search(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
				 raise(path, 'Collection cannot be empty')
			      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
      raise(path, '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')