@@ -28,6 +28,84 @@ local M = {
2828
2929local List = require 'pandoc.List'
3030
31+------------------------------------------------------------------------
32+-- Accessor objects
33+--
34+-- Create metatables which allow to access numerical indices via accessor
35+-- methods.
36+-- @section
37+-- @local
38+
39+--- Create a new indexing function.
40+-- @param template function template
41+-- @param indices list of indices, starting with the most significant
42+-- @return newly created function
43+-- @local
44+function make_indexing_function(template, indices)
45+ local loadstring = loadstring or load
46+ local bracketed = {}
47+ for i = 1, #indices do bracketed[i] = string.format('[%d]', indices[i]) end
48+ local fnstr = string.format('return ' .. template, table.concat(bracketed))
49+ return assert(loadstring(fnstr))()
50+end
51+
52+--- Create accessor functions using a function template.
53+-- @param fn_template function template in which '%s' is replacd with indices
54+-- @param accessors list of accessors
55+-- @return mapping from accessor names to accessor functions
56+-- @local
57+local function create_accessor_functions (fn_template, accessors)
58+ local res = {}
59+ function add_accessors(acc, ...)
60+ local indices = {...} -- ensure a fresh indices table
61+ if type(acc) == "string" then
62+ res[acc] = make_indexing_function(fn_template, indices)
63+ else
64+ local ind_len = #indices
65+ local unpack = table.unpack or unpack
66+ for i = 1, #(acc or {}) do
67+ indices[ind_len + 1] = i
68+ add_accessors(acc[i], unpack(indices))
69+ end
70+ end
71+ end
72+ add_accessors(accessors)
73+ return res
74+end
75+
76+--- Create a new table which allows to access numerical indices via accessor
77+-- functions.
78+-- @local
79+local function create_accessor_behavior (tag, accessors)
80+ local behavior = {tag = tag}
81+ behavior.getters = create_accessor_functions(
82+ 'function (x) return x.c%s end',
83+ accessors
84+ )
85+ behavior.setters = create_accessor_functions(
86+ 'function (x, v) x.c%s = v end',
87+ accessors
88+ )
89+ behavior.__index = function(t, k)
90+ if getmetatable(t).getters[k] then
91+ return getmetatable(t).getters[k](t)
92+ elseif k == "t" then
93+ return getmetatable(t)["tag"]
94+ else
95+ return getmetatable(t)[k]
96+ end
97+ end
98+ behavior.__newindex = function(t, k, v)
99+ if getmetatable(t).setters[k] then
100+ getmetatable(t).setters[k](t, v)
101+ else
102+ rawset(t, k, v)
103+ end
104+ end
105+ return behavior
106+end
107+
108+
31109------------------------------------------------------------------------
32110-- The base class for types
33111-- @type Type
@@ -79,7 +157,7 @@ end
79157-- @local
80158local AstElement = Type:make_subtype 'AstElement'
81159AstElement.__call = function(t, ...)
82− local success, ret = pcall(t.constructor, ...)
160+ local success, ret = pcall(t.new, t, ...)
83161 if success then
84162 return setmetatable(ret, t.behavior)
85163 else
@@ -87,6 +165,12 @@ AstElement.__call = function(t, ...)
87165 end
88166end
89167
168+function AstElement:make_subtype(...)
169+ local newtype = Type.make_subtype(self, ...)
170+ newtype.__call = self.__call
171+ return newtype
172+end
173+
90174--- Create a new constructor
91175-- @local
92176-- @param tag Tag used to identify the constructor
@@ -94,55 +178,7 @@ end
94178-- @param accessors names to use as accessors for numerical fields
95179-- @return function that constructs a new element
96180function AstElement:create_constructor(tag, fn, accessors)
97− local constr = self:make_subtype(tag, {tag = tag, getters = {}, setters = {}})
98− behavior = constr.behavior
99− behavior.__index = function(t, k)
100− if getmetatable(t).getters[k] then
101− return getmetatable(t).getters[k](t)
102− elseif k == "t" then
103− return getmetatable(t)["tag"]
104− else
105− return getmetatable(t)[k]
106− end
107− end
108− behavior.__newindex = function(t, k, v)
109− if getmetatable(t).setters[k] then
110− getmetatable(t).setters[k](t, v)
111− else
112− rawset(t, k, v)
113− end
114− end
115−
116− -- Add accessors to the metatable
117− if type(accessors) == "string" then
118− behavior.getters[accessors] = function(elem)
119− return elem.c
120− end
121− behavior.setters[accessors] = function(elem, v)
122− elem.c = v
123− end
124− else
125− for i = 1, #(accessors or {}) do
126− if type(accessors[i]) == "string" then
127− behavior.getters[accessors[i]] = function(elem)
128− return elem.c[i]
129− end
130− behavior.setters[accessors[i]] = function(elem, v)
131− elem.c[i] = v
132− end
133− else -- only two levels of nesting are supported
134− for k, v in ipairs(accessors[i]) do
135− behavior.getters[v] = function(elem)
136− return elem.c[i][k]
137− end
138− behavior.setters[v] = function(elem, v)
139− elem.c[i][k] = v
140− end
141− end
142− end
143− end
144− end
145−
181+ local constr = self:make_subtype(tag, create_accessor_behavior(tag, accessors))
146182 function constr:new(...)
147183 return setmetatable(fn(...), self.behavior)
148184 end
@@ -151,24 +187,6 @@ function AstElement:create_constructor(tag, fn, accessors)
151187 return constr
152188end
153189
154−--- Create a new element given its tag and arguments
155−-- @local
156−function AstElement.new(constr, ...)
157− local element = {}
158− local content = {...}
159− -- special case for unary constructors
160− if #content == 1 then
161− element.c = content[1]
162− -- Don't set 'c' field if no further arguments were given. This is important
163− -- for nullary constructors like `Space` and `HorizontalRule`.
164− elseif #content > 0 then
165− element.c = content
166− end
167− setmetatable(element, constr)
168− element.__index = element
169− return element
170−end
171−
172190------------------------------------------------------------------------
173191--- Pandoc Document
174192-- @section document
@@ -178,7 +196,7 @@ end
178196-- @tparam {Block,...} blocks document content
179197-- @tparam[opt] Meta meta document meta data
180198M.Pandoc = AstElement:make_subtype'Pandoc'
181−function M.Pandoc.constructor (blocks, meta)
199+function M.Pandoc:new (blocks, meta)
182200 return {
183201 blocks = List:new(blocks),
184202 meta = meta or {},
@@ -197,16 +215,13 @@ M.Doc = M.Pandoc
197215-- @function Meta
198216-- @tparam meta table table containing document meta information
199217M.Meta = AstElement:make_subtype'Meta'
200−M.Meta.constructor = function (meta) return meta end
218+function M.Meta:new (meta) return meta end
201219
202220
203221------------------------------------------------------------------------
204222-- MetaValue
205223-- @section MetaValue
206224M.MetaValue = AstElement:make_subtype('MetaValue')
207−M.MetaValue.__call = function (t, ...)
208− return t:new(...)
209−end
210225
211226--- Meta blocks
212227-- @function MetaBlocks
@@ -263,9 +278,6 @@ end
263278
264279--- Block elements
265280M.Block = AstElement:make_subtype'Block'
266−M.Block.__call = function (t, ...)
267− return t:new(...)
268−end
269281
270282--- Creates a block quote element
271283-- @function BlockQuote
@@ -437,9 +449,6 @@ M.Table = M.Block:create_constructor(
437449
438450--- Inline element class
439451M.Inline = AstElement:make_subtype'Inline'
440−M.Inline.__call = function (t, ...)
441− return t:new(...)
442−end
443452
444453--- Creates a Cite inline element
445454-- @function Cite
@@ -785,7 +794,7 @@ end
785794-- @tparam[opt] table attributes table containing string keys and values
786795-- @return element attributes
787796M.Attr = AstElement:make_subtype'Attr'
788−M.Attr.constructor = function(identifier, classes, attributes)
797+function M.Attr:new (identifier, classes, attributes)
789798 identifier = identifier or ''
790799 classes = List:new(classes or {})
791800 attributes = setmetatable(to_alist(attributes or {}), AttributeList)
@@ -816,7 +825,7 @@ M.Citation = AstElement:make_subtype'Citation'
816825-- @tparam[opt] {Inline,...} suffix citation suffix
817826-- @tparam[opt] int note_num note number
818827-- @tparam[opt] int hash hash number
819−function M.Citation.constructor (id, mode, prefix, suffix, note_num, hash)
828+function M.Citation:new (id, mode, prefix, suffix, note_num, hash)
820829 return {
821830 id = id,
822831 mode = mode,