-
Notifications
You must be signed in to change notification settings - Fork 12
/
docs.croc
352 lines (279 loc) · 13.2 KB
/
docs.croc
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
/**
This module contains the runtime interface to Croc's built-in documentation system. It defines the decorator function
which the compiler can translate doc comments into, as well as the actual documentation processing functions. It also
contains a basic doc outputting scaffold, so that you can output documentation in a human-readable format without much
extra work.
*/
module docs
local docTables = {}
local _processComment, _parseCommentText, _getMetatable =
_docstmp.processComment, _docstmp.parseCommentText, _docstmp.getMetatable
// Neat: we can actually use doc comments on _doc_ because of the way decorators work. The global _doc_ is
// defined before the decorator is called. So _doc_ can be used on itself!
/**
This is a decorator function used to attach documentation tables to objects. The compiler can attach calls to this
decorator to declarations in your code automatically by extracting documentation comments and information about the
declarations from the code.
Once the documentation table has been set for an object, you can retrieve it with docsOf, which can then be further
processed and output in a human-readable form (for instance, by using the various doc output classes).
This function is also exported in the global namespace so that you can access it unqualified (that is, both \tt{_doc_}
and \tt{docs._doc_} refer to this function).
\param[val] is the decorated object and can be any reference type.
\param[doctable] is a table, presumably one which matches the specifications for doc tables.
\param[vararg] should all be integers and are used to extract the correct sub-table from the root documentation table
(the \tt{doctable} parameter). So, for instance, using "\tt{@_doc_(someTable, 0, 2)}" on a declaration would mean that
the table \tt{someTable.children[0].children[2]} would be used as the documentation for the decorated declaration. If no
variadic arguments are given, the table itself is set as the documentation table of the object.
\returns \tt{val} as per the decorator protocol.
\throws[TypeError] if any of the \tt{varargs} are not ints, or if the value that will be set as the
doctable for \tt{val} is not a table.
*/
function _doc_(
val: table|namespace|array|memblock|function|funcdef|class|instance|thread,
doctable: table,
vararg)
{
local d = doctable
for(i; 0 .. #vararg)
{
local idx = vararg[i]
if(!isInt(idx))
throw TypeError("_doc_ - Parameter {} expected to be 'int', not '{}'".format(i + 2, typeof(idx)))
d = d.children[idx]
}
if(!isTable(d))
throw TypeError("_doc_ - Doc table is not a table, it is of type '{}'", typeof(d))
docTables[val] = d
return val
}
// Export globally
_G._doc_ = _doc_
/**
This retrieves the documentation table, if any, associated with an object.
\param[val] is the object whose docs are to be retrieved. Any type is allowed, but only reference types can have
documentation tables associated with them.
Some program elements can't have their docs directly retrieved with this function. See also \link{metamethodDocs} and
\link{childDocs}.
\returns the doc table for \tt{val} if one has been set, or \tt{null} if none has been set (or if \tt{val} is
a value type).
*/
function docsOf(val) =
docTables[val]
/**
This retrieves the documentation table, if any, associated with a metamethod in a global type metatable.
Normally, without access to the debug lib, Croc code can't actually get a direct reference to these methods (such as
the \tt{format} method of strings). This function lets you get docs for those methods without the debug lib.
\param[type] is the name of the built-in type you want to get the method's docs from.
\param[method] is the name of the method to get the docs of.
\returns the doc table for the given method if one has been set, or \tt{null} if not.
\throws[ValueError] if \tt{type} does not name a valid built-in type, or if \tt{type} has no metatable
set for it.
\throws[FieldError] if there is no method named \tt{method} in \tt{type}'s metatable.
*/
function metamethodDocs(type: string, method: string)
{
local ok, mt = _getMetatable(type)
if(!ok)
throw ValueError("Invalid type")
if(mt is null)
throw ValueError("Type has no metatable")
local func = mt[method]
if(func is null)
throw FieldError("No method '{}' in given type's metatable".format(method))
return docsOf(func)
}
/**
This retrieves the documentation table, if any, associated with a child element of a class or namespace.
Functions defined in classes and namespaces can have their docs accessed directly, but for other fields, you have to
access their doc tables through their parents' \tt{children} doctable member. This function automates that for you.
\param[obj] is the class or namespace in which the field is defined.
\param[child] is the name of the field whose docs you want to retrieve. Any field name is valid, not just non-functions.
\returns the doc table for the given field if one has been set, or \tt{null} if not.
\throws[ValueError] if \tt{obj} has no child named \tt{child}, or if \tt{obj} has no doctable.
*/
function childDocs(obj: class|namespace, child: string)
{
if(child not in obj)
throw ValueError("Object has no child named '{}'".format(child))
local docs = docsOf(obj)
if(docs is null)
throw ValueError("Object has no doc table")
foreach outerLoop(c; docs.children)
{
if(c.name == child)
return c
if(c.dittos)
{
foreach(dit; c.dittos)
{
if(dit.name == child)
return c
}
}
}
return null
}
/**
Low-level function which takes the raw text from a doc comment and a doctable (with no docs member) and parses the
doc comment, adding the appropriate members to the given doctable.
This is actually the same function that the compiler itself calls to process doc comments. Note that the doctable
that is to be passed to this function must be properly formed (with all the "standard" members, as well as any
extra kind-specific members as defined in the doc comment spec), but there must be no "docs" members at all. The
"docs" members, as well as members for other sections, will be filled in by this function.
\param[comment] is the raw text of the doc comment.
\param[doctable] is the doctable as explained above.
\returns the \tt{doctable} parameter.
\throws[SyntaxException] if parsing the comment failed. Note that in this case the \tt{doctable} may be
partially filled-in.
*/
function processComment(comment: string, doctable: table) =
_processComment(comment, doctable)
/**
Takes a string containing Croc doc comment markup, and parses it into a paragraph list.
This doesn't parse the whole text of a doc comment; rather it just parses one or more paragraphs of text. Section
commands are not allowed to appear in the text. Span and text structure commands, however, are valid.
\param[comment] is the raw markup to be parsed.
\returns an array which is a paragraph list as defined in the doc comment spec.
\throws[SyntaxException] if parsing failed.
*/
function parseCommentText(comment: string) =
_parseCommentText(comment)
/**
A class which defines a simple interface for visiting all the elements in a doctable. You can use this as a base class
for your own visitors to do things like output docs in various formats.
By default this class doesn't do much, but it does remove some of the boilerplate involved in dispatching to the various
methods. Some methods are unimplemented and must be defined in a derived class in order to work.
Note that there is another class, \link{doctools.output.OutputDocVisitor}, which implements this class's interface in a
way that makes making document outputters easier.
*/
class DocVisitor
{
/**
Visits one program item's doctable. This dispatches based on \tt{item.kind} to one of the six methods after this
one.
\throws[ValueError] if the given doctable has an invalid \tt{kind} field.
*/
function visitItem(item: table)
{
switch(item.kind)
{
case "module": :visitModule(item); break
case "function": :visitFunction(item); break
case "class": :visitClass(item); break
case "namespace": :visitNamespace(item); break
case "field": :visitField(item); break
case "variable": :visitVariable(item); break
default: throw ValueError("Malformed documentation (invalid doctable kind '{}')".format(item.kind))
}
}
/**
These methods are called by \link{visitItem}. You must implement these methods yourself.
*/
function visitModule(item: table) { throw NotImplementedError() }
function visitClass(item: table) { throw NotImplementedError() } /// ditto
function visitNamespace(item: table) { throw NotImplementedError() } /// ditto
function visitFunction(item: table) { throw NotImplementedError() } /// ditto
function visitField(item: table) { throw NotImplementedError() } /// ditto
function visitVariable(item: table) { throw NotImplementedError() } /// ditto
/**
A convenience method for doctables which have children (such as modules, classes, and namespaces). This just loops
over the doctable's \tt{children} field and calls \link{visitItem} on each one.
*/
function visitChildren(item: table)
{
foreach(child; item.children)
:visitItem(child)
}
/**
A convenience method for visiting a list of paragraphs (plist). This just loops over the plist and calls
\link{visitParagraph} on each one.
*/
function visitPlist(plist: array)
{
foreach(par; plist)
:visitParagraph(par)
}
/**
Visits one paragraph of documentation. By default, just calls \link{visitParagraphElements} with \tt{par} as the
argument, but often you will want to do something at the beginning/end of a paragraph (such as insert indentation
or newlines), so you can override this method to do so.
*/
function visitParagraph(par: array)
{
:visitParagraphElements(par)
}
/**
Visits an array of paragraph elements. You can use this method to implement the text visitor methods which follow,
and this method will dispatch to those visitor methods.
*/
function visitParagraphElements(elems: array)
{
foreach(elem; elems)
{
if(isString(elem))
:visitText(elem)
else if(isArray(elem))
{
if(#elem == 0)
throw ValueError("Malformed documentation (invalid paragraph element)")
local type = elem[0]
switch(type)
{
case "code": :visitCode(elem[1], elem[2]); break
case "verbatim": :visitVerbatim(elem[1], elem[2]); break
case "blist": :visitBlist(elem[1..]); break
case "nlist": :visitNlist(elem[1], elem[2..]); break
case "dlist": :visitDlist(elem[1..]); break
case "table": :visitTable(elem[1..]); break
case "b": :visitBold(elem[1..]); break
case "em": :visitEmphasis(elem[1..]); break
case "link": :visitLink(elem[1], elem[2..]); break
case "s": :visitStrikethrough(elem[1..]); break
case "sub": :visitSubscript(elem[1..]); break
case "sup": :visitSuperscript(elem[1..]); break
case "tt": :visitMonospace(elem[1..]); break
case "u": :visitUnderline(elem[1..]); break
default:
if(isString(type) && type.startsWith("_"))
:visitCustomSpan(type, elem[1..])
else
throw ValueError("Malformed documentation (invalid paragraph element type)")
}
}
else
throw ValueError("Malformed documentation (invalid paragraph element)")
}
}
/// Visits a segment of plain text that appears in a paragraph. Must be overridden.
function visitText(elem: string) { throw NotImplementedError() }
/// Visits a code snippet. Must be overridden.
function visitCode(language: string, contents: string) { throw NotImplementedError() }
/// Visits a verbatim block. Must be overridden.
function visitVerbatim(type: string, contents: string) { throw NotImplementedError() }
/// Visits a bulleted list. Must be overridden.
function visitBlist(items: array) { throw NotImplementedError() }
/// Visits a numbered list. Must be overridden.
function visitNlist(type: string, items: array) { throw NotImplementedError() }
/// Visits a definition list. Must be overridden.
function visitDlist(items: array) { throw NotImplementedError() }
/// Visits a table. Must be overridden.
function visitTable(rows: array) { throw NotImplementedError() }
/// Visits an bold text span. Must be overridden.
function visitBold(contents: array) { throw NotImplementedError() }
/// Visits an emphasis text span. Must be overridden.
function visitEmphasis(contents: array) { throw NotImplementedError() }
/// Visits a link text span. Must be overridden.
function visitLink(link: string, contents: array) { throw NotImplementedError() }
/// Visits an strikethrough text span. Must be overridden.
function visitStrikethrough(contents: array) { throw NotImplementedError() }
/// Visits a subscript text span. Must be overridden.
function visitSubscript(contents: array) { throw NotImplementedError() }
/// Visits a superscript text span. Must be overridden.
function visitSuperscript(contents: array) { throw NotImplementedError() }
/// Visits a monospace text span. Must be overridden.
function visitMonospace(contents: array) { throw NotImplementedError() }
/// Visits an underline text span. Must be overridden.
function visitUnderline(contents: array) { throw NotImplementedError() }
/// Visits a custom text span. Must be overridden.
function visitCustomSpan(type: string, contents: array) { throw NotImplementedError() }
}