schema-markdown 1.1.5 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -41
- package/lib/parser.js +481 -531
- package/lib/schema.js +21 -41
- package/lib/schemaUtil.js +1 -1
- package/lib/typeModel.js +6 -23
- package/package.json +6 -6
package/lib/parser.js
CHANGED
|
@@ -3,108 +3,459 @@
|
|
|
3
3
|
|
|
4
4
|
/** @module lib/parser */
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {validateTypeModelErrors} from './schemaUtil.js';
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
// Built-in types
|
|
10
|
+
const builtinTypes = new Set(['bool', 'date', 'datetime', 'float', 'int', 'object', 'string', 'uuid']);
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
// Schema Markdown regex
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
+
const rPartId = '(?:[A-Za-z]\\w*)';
|
|
15
|
+
const rPartAttrGroup =
|
|
14
16
|
'(?:(?<nullable>nullable)' +
|
|
15
17
|
'|(?<op><=|<|>|>=|==)\\s*(?<opnum>-?\\d+(?:\\.\\d+)?)' +
|
|
16
18
|
'|(?<ltype>len)\\s*(?<lop><=|<|>|>=|==)\\s*(?<lopnum>\\d+))';
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
19
|
+
const rPartAttr = rPartAttrGroup.replace(/\(\?<[^>]+>/g, '(?:');
|
|
20
|
+
const rPartAttrs = `(?:${rPartAttr}(?:\\s*,\\s*${rPartAttr})*)`;
|
|
21
|
+
const rAttrGroup = new RegExp(rPartAttrGroup);
|
|
22
|
+
const rFindAttrs = new RegExp(`${rPartAttr}(?:\\s*,\\s*|\\s*$)`, 'g');
|
|
23
|
+
const rLineCont = /\\\s*$/;
|
|
24
|
+
const rComment = /^\s*(?:#-.*|#(?<doc>.*))?$/;
|
|
25
|
+
const rGroup = /^group(?:\s+"(?<group>.+?)")?\s*$/;
|
|
26
|
+
const rAction = new RegExp(`^action\\s+(?<id>${rPartId})`);
|
|
27
|
+
const rPartBaseIds = `(?:\\s*\\(\\s*(?<baseIds>${rPartId}(?:\\s*,\\s*${rPartId})*)\\s*\\)\\s*)`;
|
|
28
|
+
const rBaseIdsSplit = /\s*,\s*/;
|
|
29
|
+
const rDefinition = new RegExp(`^(?<type>struct|union|enum)\\s+(?<id>${rPartId})${rPartBaseIds}?\\s*$`);
|
|
30
|
+
const rSection = new RegExp(`^\\s+(?<type>path|query|input|output|errors)${rPartBaseIds}?\\s*$`);
|
|
31
|
+
const rSectionPlain = /^\s+(?<type>urls)\s*$/;
|
|
32
|
+
const rPartTypedef =
|
|
33
|
+
`(?<type>${rPartId})` +
|
|
34
|
+
`(?:\\s*\\(\\s*(?<attrs>${rPartAttrs})\\s*\\))?` +
|
|
33
35
|
'(?:' +
|
|
34
|
-
|
|
36
|
+
`(?:\\s*\\[\\s*(?<array>${rPartAttrs}?)\\s*\\])?` +
|
|
35
37
|
'|' +
|
|
36
38
|
'(?:' +
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
`\\s*:\\s*(?<dictValueType>${rPartId})` +
|
|
40
|
+
`(?:\\s*\\(\\s*(?<dictValueAttrs>${rPartAttrs})\\s*\\))?` +
|
|
39
41
|
')?' +
|
|
40
|
-
|
|
42
|
+
`(?:\\s*\\{\\s*(?<dict>${rPartAttrs}?)\\s*\\})?` +
|
|
41
43
|
')' +
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
const
|
|
44
|
+
`\\s+(?<id>${rPartId})`;
|
|
45
|
+
const rTypedef = new RegExp(`^typedef\\s+${rPartTypedef}\\s*$`);
|
|
46
|
+
const rMember = new RegExp(`^\\s+(?<optional>optional\\s+)?${rPartTypedef}\\s*$`);
|
|
47
|
+
const rValue = new RegExp(`^\\s+(?<id>${rPartId})\\s*$`);
|
|
48
|
+
const rValueQuoted = /^\s+"(?<id>.*?)"\s*$/;
|
|
49
|
+
const rURL = /^\s+(?<method>[A-Za-z]+|\*)(?:\s+(?<path>\/[^\s]*))?/;
|
|
50
|
+
const rLineSplit = /\r?\n/;
|
|
48
51
|
|
|
49
52
|
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Parse Schema Markdown from a string or and iterator of strings
|
|
55
|
+
*
|
|
56
|
+
* @param {string|string[]} text - The Schema Markdown text
|
|
57
|
+
* @param {Object} [options.types=''] - The [type model]{@link https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types'}
|
|
58
|
+
* @param {string} [options.filename=''] - The name of file being parsed (for error messages)
|
|
59
|
+
* @param {boolean} [options.validate=true] - If true, validate after parsing
|
|
60
|
+
* @returns {Object} The [type model]{@link https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types'}
|
|
61
|
+
* @throws [SchemaMarkdownParserError]{@link module:lib/parser.SchemaMarkdownParserError}
|
|
62
|
+
*/
|
|
63
|
+
export function parseSchemaMarkdown(text, {types = {}, filename = '', validate = true} = {}) {
|
|
64
|
+
// Current parser state
|
|
65
|
+
const errorMap = {};
|
|
66
|
+
const filepos = {};
|
|
67
|
+
let action = null;
|
|
68
|
+
let urls = null;
|
|
69
|
+
let userType = null;
|
|
70
|
+
let doc = [];
|
|
71
|
+
let docGroup = null;
|
|
72
|
+
let linenum = 0;
|
|
73
|
+
|
|
74
|
+
// Helper function to add an error message
|
|
75
|
+
const addError = (msg, errorFilename, errorLinenum) => {
|
|
76
|
+
const errorMsg = `${errorFilename}:${errorLinenum}: error: ${msg}`;
|
|
77
|
+
errorMap[errorMsg] = [errorFilename, errorLinenum, errorMsg];
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Helper function to get documentation strings
|
|
81
|
+
const getDoc = () => {
|
|
82
|
+
let result = null;
|
|
83
|
+
if (doc.length) {
|
|
84
|
+
result = doc;
|
|
85
|
+
doc = [];
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Line-split all script text
|
|
91
|
+
const lines = [];
|
|
92
|
+
if (typeof text === 'string') {
|
|
93
|
+
lines.push(...text.split(rLineSplit));
|
|
94
|
+
} else {
|
|
95
|
+
for (const textPart of text) {
|
|
96
|
+
lines.push(...textPart.split(rLineSplit));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
lines.push('');
|
|
52
100
|
|
|
101
|
+
// Process each line
|
|
102
|
+
const lineContinuation = [];
|
|
103
|
+
for (const linePart of lines) {
|
|
104
|
+
linenum += 1;
|
|
53
105
|
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
106
|
+
// Line continuation?
|
|
107
|
+
const linePartNoContinuation = linePart.replace(rLineCont, '');
|
|
108
|
+
if (lineContinuation.length || linePartNoContinuation !== linePart) {
|
|
109
|
+
lineContinuation.push(linePartNoContinuation);
|
|
110
|
+
}
|
|
111
|
+
if (linePartNoContinuation !== linePart) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
let line;
|
|
115
|
+
if (lineContinuation.length) {
|
|
116
|
+
line = lineContinuation.join('');
|
|
117
|
+
lineContinuation.length = 0;
|
|
118
|
+
} else {
|
|
119
|
+
line = linePart;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Match syntax
|
|
123
|
+
let matchName = 'comment';
|
|
124
|
+
let match = line.match(rComment);
|
|
125
|
+
if (match === null) {
|
|
126
|
+
matchName = 'group';
|
|
127
|
+
match = line.match(rGroup);
|
|
128
|
+
}
|
|
129
|
+
if (match === null) {
|
|
130
|
+
matchName = 'action';
|
|
131
|
+
match = line.match(rAction);
|
|
132
|
+
}
|
|
133
|
+
if (match === null) {
|
|
134
|
+
matchName = 'definition';
|
|
135
|
+
match = line.match(rDefinition);
|
|
136
|
+
}
|
|
137
|
+
if (match === null && action !== null) {
|
|
138
|
+
matchName = 'section';
|
|
139
|
+
match = line.match(rSection);
|
|
140
|
+
}
|
|
141
|
+
if (match === null && action !== null) {
|
|
142
|
+
matchName = 'sectionPlain';
|
|
143
|
+
match = line.match(rSectionPlain);
|
|
144
|
+
}
|
|
145
|
+
if (match === null && userType !== null && 'enum' in userType) {
|
|
146
|
+
matchName = 'value';
|
|
147
|
+
match = line.match(rValue);
|
|
148
|
+
if (match === null) {
|
|
149
|
+
match = line.match(rValueQuoted);
|
|
61
150
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
151
|
+
}
|
|
152
|
+
if (match === null && userType !== null && 'struct' in userType) {
|
|
153
|
+
matchName = 'member';
|
|
154
|
+
match = line.match(rMember);
|
|
155
|
+
}
|
|
156
|
+
if (match === null && urls !== null) {
|
|
157
|
+
matchName = 'urls';
|
|
158
|
+
match = line.match(rURL);
|
|
159
|
+
}
|
|
160
|
+
if (match === null) {
|
|
161
|
+
matchName = 'typedef';
|
|
162
|
+
match = line.match(rTypedef);
|
|
163
|
+
}
|
|
164
|
+
if (match === null) {
|
|
165
|
+
matchName = null;
|
|
166
|
+
}
|
|
65
167
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
168
|
+
// Comment?
|
|
169
|
+
if (matchName === 'comment') {
|
|
170
|
+
const docString = match.groups.doc;
|
|
171
|
+
if (typeof docString !== 'undefined') {
|
|
172
|
+
doc.push(!docString.startsWith(' ') ? docString : docString.slice(1));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Documentation group?
|
|
176
|
+
} else if (matchName === 'group') {
|
|
177
|
+
docGroup = match.groups.group;
|
|
178
|
+
if (typeof docGroup !== 'undefined') {
|
|
179
|
+
docGroup = docGroup.trim();
|
|
180
|
+
} else {
|
|
181
|
+
docGroup = null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Action?
|
|
185
|
+
} else if (matchName === 'action') {
|
|
186
|
+
const actionId = match.groups.id;
|
|
187
|
+
|
|
188
|
+
// Action already defined?
|
|
189
|
+
if (actionId in types) {
|
|
190
|
+
addError(`Redefinition of action '${actionId}'`, filename, linenum);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Clear parser state
|
|
194
|
+
urls = null;
|
|
195
|
+
userType = null;
|
|
196
|
+
const actionDoc = getDoc();
|
|
197
|
+
|
|
198
|
+
// Create the new action
|
|
199
|
+
action = {'name': actionId};
|
|
200
|
+
types[actionId] = {'action': action};
|
|
201
|
+
if (actionDoc !== null) {
|
|
202
|
+
action.doc = actionDoc;
|
|
203
|
+
}
|
|
204
|
+
if (docGroup !== null) {
|
|
205
|
+
action.docGroup = docGroup;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Definition?
|
|
209
|
+
} else if (matchName === 'definition') {
|
|
210
|
+
const definitionString = match.groups.type;
|
|
211
|
+
const definitionId = match.groups.id;
|
|
212
|
+
const definitionBaseIds = match.groups.baseIds;
|
|
213
|
+
|
|
214
|
+
// Type already defined?
|
|
215
|
+
if (builtinTypes.has(definitionId) || definitionId in types) {
|
|
216
|
+
addError(`Redefinition of type '${definitionId}'`, filename, linenum);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Clear parser state
|
|
220
|
+
action = null;
|
|
221
|
+
urls = null;
|
|
222
|
+
const definitionDoc = getDoc();
|
|
223
|
+
|
|
224
|
+
// Struct definition
|
|
225
|
+
if (definitionString === 'struct' || definitionString === 'union') {
|
|
226
|
+
// Create the new struct type
|
|
227
|
+
const struct = {'name': definitionId};
|
|
228
|
+
userType = {'struct': struct};
|
|
229
|
+
types[definitionId] = userType;
|
|
230
|
+
if (definitionDoc !== null) {
|
|
231
|
+
struct.doc = definitionDoc;
|
|
232
|
+
}
|
|
233
|
+
if (docGroup !== null) {
|
|
234
|
+
struct.docGroup = docGroup;
|
|
235
|
+
}
|
|
236
|
+
if (definitionString === 'union') {
|
|
237
|
+
struct.union = true;
|
|
238
|
+
}
|
|
239
|
+
if (typeof definitionBaseIds !== 'undefined') {
|
|
240
|
+
struct.bases = definitionBaseIds.split(rBaseIdsSplit);
|
|
80
241
|
}
|
|
242
|
+
|
|
243
|
+
// Enum definition
|
|
81
244
|
} else {
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
245
|
+
// definitionString == 'enum':
|
|
246
|
+
// Create the new enum type
|
|
247
|
+
const enum_ = {'name': definitionId};
|
|
248
|
+
userType = {'enum': enum_};
|
|
249
|
+
types[definitionId] = userType;
|
|
250
|
+
if (definitionDoc !== null) {
|
|
251
|
+
enum_.doc = definitionDoc;
|
|
252
|
+
}
|
|
253
|
+
if (docGroup !== null) {
|
|
254
|
+
enum_.docGroup = docGroup;
|
|
255
|
+
}
|
|
256
|
+
if (typeof definitionBaseIds !== 'undefined') {
|
|
257
|
+
enum_.bases = definitionBaseIds.split(rBaseIdsSplit);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Record the definition's line number
|
|
262
|
+
filepos[definitionId] = linenum;
|
|
263
|
+
|
|
264
|
+
// Action section?
|
|
265
|
+
} else if (matchName === 'section') {
|
|
266
|
+
const sectionString = match.groups.type;
|
|
267
|
+
const sectionBaseIds = match.groups.baseIds;
|
|
268
|
+
|
|
269
|
+
// Action section redefinition?
|
|
270
|
+
if (sectionString in action) {
|
|
271
|
+
addError(`Redefinition of action ${sectionString}`, filename, linenum);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Clear parser state
|
|
275
|
+
urls = null;
|
|
276
|
+
|
|
277
|
+
// Set the action section type
|
|
278
|
+
const sectionTypeName = `${action.name}_${sectionString}`;
|
|
279
|
+
action[sectionString] = sectionTypeName;
|
|
280
|
+
if (sectionString === 'errors') {
|
|
281
|
+
const enum_ = {'name': sectionTypeName};
|
|
282
|
+
userType = {'enum': enum_};
|
|
283
|
+
types[sectionTypeName] = userType;
|
|
284
|
+
if (typeof sectionBaseIds !== 'undefined') {
|
|
285
|
+
enum_.bases = sectionBaseIds.split(rBaseIdsSplit);
|
|
286
|
+
}
|
|
287
|
+
} else {
|
|
288
|
+
const struct = {'name': sectionTypeName};
|
|
289
|
+
userType = {'struct': struct};
|
|
290
|
+
types[sectionTypeName] = userType;
|
|
291
|
+
if (typeof sectionBaseIds !== 'undefined') {
|
|
292
|
+
struct.bases = sectionBaseIds.split(rBaseIdsSplit);
|
|
94
293
|
}
|
|
95
294
|
}
|
|
295
|
+
|
|
296
|
+
// Record the definition's line number
|
|
297
|
+
filepos[sectionTypeName] = linenum;
|
|
298
|
+
|
|
299
|
+
// Plain action section?
|
|
300
|
+
} else if (matchName === 'sectionPlain') {
|
|
301
|
+
const sectionString = match.groups.type;
|
|
302
|
+
|
|
303
|
+
// Action section redefinition?
|
|
304
|
+
if (sectionString in action) {
|
|
305
|
+
addError(`Redefinition of action ${sectionString}`, filename, linenum);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Clear parser state
|
|
309
|
+
userType = null;
|
|
310
|
+
|
|
311
|
+
// Update the parser state
|
|
312
|
+
urls = [];
|
|
313
|
+
|
|
314
|
+
// Enum value?
|
|
315
|
+
} else if (matchName === 'value') {
|
|
316
|
+
const valueString = match.groups.id;
|
|
317
|
+
|
|
318
|
+
// Add the enum value
|
|
319
|
+
const enum_ = userType.enum;
|
|
320
|
+
if (!('values' in enum_)) {
|
|
321
|
+
enum_.values = [];
|
|
322
|
+
}
|
|
323
|
+
const enumValue = {'name': valueString};
|
|
324
|
+
enum_.values.push(enumValue);
|
|
325
|
+
const enumValueDoc = getDoc();
|
|
326
|
+
if (enumValueDoc !== null) {
|
|
327
|
+
enumValue.doc = enumValueDoc;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Record the definition's line number
|
|
331
|
+
filepos[`${enum_.name}.${valueString}`] = linenum;
|
|
332
|
+
|
|
333
|
+
// Struct member?
|
|
334
|
+
} else if (matchName === 'member') {
|
|
335
|
+
const optional = typeof match.groups.optional !== 'undefined';
|
|
336
|
+
const memberName = match.groups.id;
|
|
337
|
+
|
|
338
|
+
// Add the member
|
|
339
|
+
const {struct} = userType;
|
|
340
|
+
if (!('members' in struct)) {
|
|
341
|
+
struct.members = [];
|
|
342
|
+
}
|
|
343
|
+
const [memberType, memberAttr] = parseTypedef(match);
|
|
344
|
+
const memberDoc = getDoc();
|
|
345
|
+
const member = {
|
|
346
|
+
'name': memberName,
|
|
347
|
+
'type': memberType
|
|
348
|
+
};
|
|
349
|
+
struct.members.push(member);
|
|
350
|
+
if (memberAttr !== null) {
|
|
351
|
+
member.attr = memberAttr;
|
|
352
|
+
}
|
|
353
|
+
if (memberDoc !== null) {
|
|
354
|
+
member.doc = memberDoc;
|
|
355
|
+
}
|
|
356
|
+
if (optional) {
|
|
357
|
+
member.optional = true;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Record the definition's line number
|
|
361
|
+
filepos[`${struct.name}.${memberName}`] = linenum;
|
|
362
|
+
|
|
363
|
+
// URL?
|
|
364
|
+
} else if (matchName === 'urls') {
|
|
365
|
+
const {method, path} = match.groups;
|
|
366
|
+
|
|
367
|
+
// Create the action URL object
|
|
368
|
+
const actionUrl = {};
|
|
369
|
+
if (method !== '*') {
|
|
370
|
+
actionUrl.method = method;
|
|
371
|
+
}
|
|
372
|
+
if (typeof path !== 'undefined') {
|
|
373
|
+
actionUrl.path = path;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Duplicate URL?
|
|
377
|
+
if (urls.some((url) => url.method === actionUrl.method && url.path === actionUrl.path)) {
|
|
378
|
+
addError(`Duplicate URL: ${method} ${'path' in actionUrl ? actionUrl.path : ''}`, filename, linenum);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Add the URL
|
|
382
|
+
if (!('urls' in action)) {
|
|
383
|
+
action.urls = urls;
|
|
384
|
+
}
|
|
385
|
+
urls.push(actionUrl);
|
|
386
|
+
|
|
387
|
+
// Typedef?
|
|
388
|
+
} else if (matchName === 'typedef') {
|
|
389
|
+
const definitionId = match.groups.id;
|
|
390
|
+
|
|
391
|
+
// Type already defined?
|
|
392
|
+
if (builtinTypes.has(definitionId) || definitionId in types) {
|
|
393
|
+
addError(`Redefinition of type '${definitionId}'`, filename, linenum);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Clear parser state
|
|
397
|
+
action = null;
|
|
398
|
+
urls = null;
|
|
399
|
+
userType = null;
|
|
400
|
+
const typedefDoc = getDoc();
|
|
401
|
+
|
|
402
|
+
// Create the typedef
|
|
403
|
+
const [typedefType, typedefAttr] = parseTypedef(match);
|
|
404
|
+
const typedef = {
|
|
405
|
+
'name': definitionId,
|
|
406
|
+
'type': typedefType
|
|
407
|
+
};
|
|
408
|
+
types[definitionId] = {'typedef': typedef};
|
|
409
|
+
if (typedefAttr !== null) {
|
|
410
|
+
typedef.attr = typedefAttr;
|
|
411
|
+
}
|
|
412
|
+
if (typedefDoc !== null) {
|
|
413
|
+
typedef.doc = typedefDoc;
|
|
414
|
+
}
|
|
415
|
+
if (docGroup !== null) {
|
|
416
|
+
typedef.docGroup = docGroup;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Record the definition's line number
|
|
420
|
+
filepos[definitionId] = linenum;
|
|
421
|
+
|
|
422
|
+
// Unrecognized line syntax
|
|
423
|
+
} else {
|
|
424
|
+
addError('Syntax error', filename, linenum);
|
|
96
425
|
}
|
|
97
426
|
}
|
|
98
|
-
return attrs;
|
|
99
|
-
}
|
|
100
427
|
|
|
428
|
+
// Validate the type model, if requested
|
|
429
|
+
if (validate) {
|
|
430
|
+
for (const [typeName, memberName, errorMsg] of validateTypeModelErrors(types)) {
|
|
431
|
+
let errorFilename = filename;
|
|
432
|
+
let errorLinenum = null;
|
|
433
|
+
if (memberName !== null) {
|
|
434
|
+
errorLinenum = filepos[`${typeName}.${memberName}`] ?? null;
|
|
435
|
+
}
|
|
436
|
+
if (errorLinenum === null) {
|
|
437
|
+
errorLinenum = filepos[typeName] ?? null;
|
|
438
|
+
}
|
|
439
|
+
if (errorLinenum === null) {
|
|
440
|
+
errorFilename = '';
|
|
441
|
+
errorLinenum = 1;
|
|
442
|
+
}
|
|
443
|
+
addError(errorMsg, errorFilename, errorLinenum);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
101
446
|
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
if (
|
|
105
|
-
|
|
447
|
+
// Raise a parser exception if there are any errors
|
|
448
|
+
const errors = Array.from(Object.values(errorMap)).sort(compareTuple).map(([,, msg]) => msg);
|
|
449
|
+
if (errors.length) {
|
|
450
|
+
throw new SchemaMarkdownParserError(errors);
|
|
106
451
|
}
|
|
107
|
-
|
|
452
|
+
|
|
453
|
+
return types;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
function compareTuple(v1, v2) {
|
|
458
|
+
return v1.reduce((tot, val, idx) => (tot !== 0 ? tot : (val < v2[idx] ? -1 : (val > v2[idx] ? 1 : 0))), 0);
|
|
108
459
|
}
|
|
109
460
|
|
|
110
461
|
|
|
@@ -159,9 +510,61 @@ function parseTypedef(matchTypedef) {
|
|
|
159
510
|
}
|
|
160
511
|
|
|
161
512
|
|
|
162
|
-
// Helper function to
|
|
163
|
-
|
|
164
|
-
|
|
513
|
+
// Helper function to create a type model
|
|
514
|
+
function createType(typeName) {
|
|
515
|
+
if (builtinTypes.has(typeName)) {
|
|
516
|
+
return {'builtin': typeName};
|
|
517
|
+
}
|
|
518
|
+
return {'user': typeName};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
// Helper function to parse an attributes string - returns an attributes model
|
|
523
|
+
function parseAttr(attrsString) {
|
|
524
|
+
let attrs = null;
|
|
525
|
+
if (typeof attrsString !== 'undefined') {
|
|
526
|
+
for (const [attrString] of attrsString.matchAll(rFindAttrs)) {
|
|
527
|
+
if (attrs === null) {
|
|
528
|
+
attrs = {};
|
|
529
|
+
}
|
|
530
|
+
const matchAttr = attrString.match(rAttrGroup);
|
|
531
|
+
const attrOp = matchAttr.groups.op;
|
|
532
|
+
const attrLengthOp = matchAttr.groups.lop;
|
|
533
|
+
|
|
534
|
+
if (typeof matchAttr.groups.nullable !== 'undefined') {
|
|
535
|
+
attrs.nullable = true;
|
|
536
|
+
} else if (typeof attrOp !== 'undefined') {
|
|
537
|
+
const attrValue = parseFloat(matchAttr.groups.opnum);
|
|
538
|
+
if (attrOp === '<') {
|
|
539
|
+
attrs.lt = attrValue;
|
|
540
|
+
} else if (attrOp === '<=') {
|
|
541
|
+
attrs.lte = attrValue;
|
|
542
|
+
} else if (attrOp === '>') {
|
|
543
|
+
attrs.gt = attrValue;
|
|
544
|
+
} else if (attrOp === '>=') {
|
|
545
|
+
attrs.gte = attrValue;
|
|
546
|
+
} else {
|
|
547
|
+
attrs.eq = attrValue;
|
|
548
|
+
}
|
|
549
|
+
} else {
|
|
550
|
+
// typeof attrLengthOp !== 'undefined'
|
|
551
|
+
const attrValue = parseInt(matchAttr.groups.lopnum, 10);
|
|
552
|
+
if (attrLengthOp === '<') {
|
|
553
|
+
attrs.lenLT = attrValue;
|
|
554
|
+
} else if (attrLengthOp === '<=') {
|
|
555
|
+
attrs.lenLTE = attrValue;
|
|
556
|
+
} else if (attrLengthOp === '>') {
|
|
557
|
+
attrs.lenGT = attrValue;
|
|
558
|
+
} else if (attrLengthOp === '>=') {
|
|
559
|
+
attrs.lenGTE = attrValue;
|
|
560
|
+
} else {
|
|
561
|
+
attrs.lenEq = attrValue;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return attrs;
|
|
567
|
+
}
|
|
165
568
|
|
|
166
569
|
|
|
167
570
|
/**
|
|
@@ -180,456 +583,3 @@ export class SchemaMarkdownParserError extends Error {
|
|
|
180
583
|
this.errors = errors;
|
|
181
584
|
}
|
|
182
585
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* The parser class for Schema Markdown. Parsing can occur at initialization time or by calling the
|
|
187
|
-
* "parseString" method, which can be called repeatedly.
|
|
188
|
-
*
|
|
189
|
-
* @property {Object} types - The map of user type name to user type model
|
|
190
|
-
* @property {Object} errorMap - Set of filename, linenum, and error-message tuples
|
|
191
|
-
* @property {Object} filepos - Map of definition-name to filename/linenum-tuples
|
|
192
|
-
*/
|
|
193
|
-
export class SchemaMarkdownParser {
|
|
194
|
-
/**
|
|
195
|
-
* Create a Schema Markdown parser instance
|
|
196
|
-
*
|
|
197
|
-
* @param {string} [text=null]: An optional Schema Markdown string to parse
|
|
198
|
-
* @param {Object} [types=null]: An optional map of user type name to user type model
|
|
199
|
-
* @throws {SchemaMarkdownParserError}
|
|
200
|
-
*/
|
|
201
|
-
constructor(text = null, types = null) {
|
|
202
|
-
this.types = types !== null ? types : {};
|
|
203
|
-
this.errorMap = {};
|
|
204
|
-
this.filepos = {};
|
|
205
|
-
|
|
206
|
-
// Parse the Schema Markdown string, if any
|
|
207
|
-
if (text !== null) {
|
|
208
|
-
this.parseString(text);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
addError(msg, filename, linenum) {
|
|
213
|
-
const errorMsg = `${filename}:${linenum}: error: ${msg}`;
|
|
214
|
-
this.errorMap[errorMsg] = [filename, linenum, errorMsg];
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
getFilepos(typeName, typeKey) {
|
|
218
|
-
let filepos = null;
|
|
219
|
-
if (typeKey !== null) {
|
|
220
|
-
const key = `${typeName}.${typeKey}`;
|
|
221
|
-
filepos = key in this.filepos ? this.filepos[key] : null;
|
|
222
|
-
}
|
|
223
|
-
if (filepos === null) {
|
|
224
|
-
filepos = typeName in this.filepos ? this.filepos[typeName] : null;
|
|
225
|
-
}
|
|
226
|
-
if (filepos === null) {
|
|
227
|
-
filepos = ['', 1];
|
|
228
|
-
}
|
|
229
|
-
return filepos;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* The list of parser error strings
|
|
234
|
-
*/
|
|
235
|
-
get errors() {
|
|
236
|
-
return Array.from(Object.values(this.errorMap)).sort(compareTuple).map(([,, msg]) => msg);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Finalize a parsing operation. You only need to call this method if you set the "parse"
|
|
241
|
-
* method's "finalize" argument to false.
|
|
242
|
-
*
|
|
243
|
-
* @throws {SchemaMarkdownParserError}
|
|
244
|
-
*/
|
|
245
|
-
finalize() {
|
|
246
|
-
// Do the finalization
|
|
247
|
-
for (const [typeName, memberName, errorMsg] of validateTypeModelTypesErrors(this.types)) {
|
|
248
|
-
this.addError(errorMsg, ...this.getFilepos(typeName, memberName));
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Raise a parser exception if there are any errors
|
|
252
|
-
const {errors} = this;
|
|
253
|
-
if (errors.length) {
|
|
254
|
-
throw new SchemaMarkdownParserError(errors);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Parse a Schema Markdown string. This method can be called repeatedly.
|
|
260
|
-
*
|
|
261
|
-
* @param {string} text - The Schema Markdown string
|
|
262
|
-
* @param {string} [filename=''] - The name of file being parsed (for error messages)
|
|
263
|
-
* @param {boolean} [finalize=true] - If true, resolve names after parsing. Be sure to call the
|
|
264
|
-
* "finalize" method when finished.
|
|
265
|
-
* @throws {SchemaMarkdownParserError}
|
|
266
|
-
*/
|
|
267
|
-
parseString(text, filename = '', finalize = true) {
|
|
268
|
-
this.parse(text.split(/\r?\n/), filename, finalize);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Parse Schema Markdown from an iterator of line strings (e.g an input stream). This method can
|
|
273
|
-
* be called repeatedly.
|
|
274
|
-
*
|
|
275
|
-
* @param {string[]} lines - An array of Schema Markdown line strings
|
|
276
|
-
* @param {string} [filename=''] - The name of file being parsed (for error messages)
|
|
277
|
-
* @param {boolean} [finalize=true] - If true, resolve names after parsing. Be sure to call the
|
|
278
|
-
* "finalize" method when finished.
|
|
279
|
-
* @throws {SchemaMarkdownParserError}
|
|
280
|
-
*/
|
|
281
|
-
parse(lines, filename = '', finalize = true) {
|
|
282
|
-
// Current parser state
|
|
283
|
-
let action = null;
|
|
284
|
-
let urls = null;
|
|
285
|
-
let userType = null;
|
|
286
|
-
let doc = [];
|
|
287
|
-
let docGroup = null;
|
|
288
|
-
let linenum = 0;
|
|
289
|
-
|
|
290
|
-
// Helper function to get documentation strings
|
|
291
|
-
const getDoc = () => {
|
|
292
|
-
let result = null;
|
|
293
|
-
if (doc.length) {
|
|
294
|
-
result = doc;
|
|
295
|
-
doc = [];
|
|
296
|
-
}
|
|
297
|
-
return result;
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
// Process each line
|
|
301
|
-
const lineContinuation = [];
|
|
302
|
-
const lineGroups = [lines, ['']];
|
|
303
|
-
for (const lineGroup of lineGroups) {
|
|
304
|
-
for (const linePart of lineGroup) {
|
|
305
|
-
linenum += 1;
|
|
306
|
-
|
|
307
|
-
// Line continuation?
|
|
308
|
-
const linePartNoContinuation = linePart.replace(RE_LINE_CONT, '');
|
|
309
|
-
if (lineContinuation.length || linePartNoContinuation !== linePart) {
|
|
310
|
-
lineContinuation.push(linePartNoContinuation);
|
|
311
|
-
}
|
|
312
|
-
if (linePartNoContinuation !== linePart) {
|
|
313
|
-
continue;
|
|
314
|
-
}
|
|
315
|
-
let line;
|
|
316
|
-
if (lineContinuation.length) {
|
|
317
|
-
line = lineContinuation.join('');
|
|
318
|
-
lineContinuation.length = 0;
|
|
319
|
-
} else {
|
|
320
|
-
line = linePart;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Match syntax
|
|
324
|
-
let matchName = 'comment';
|
|
325
|
-
let match = line.match(RE_COMMENT);
|
|
326
|
-
if (match === null) {
|
|
327
|
-
matchName = 'group';
|
|
328
|
-
match = line.match(RE_GROUP);
|
|
329
|
-
}
|
|
330
|
-
if (match === null) {
|
|
331
|
-
matchName = 'action';
|
|
332
|
-
match = line.match(RE_ACTION);
|
|
333
|
-
}
|
|
334
|
-
if (match === null) {
|
|
335
|
-
matchName = 'definition';
|
|
336
|
-
match = line.match(RE_DEFINITION);
|
|
337
|
-
}
|
|
338
|
-
if (match === null && action !== null) {
|
|
339
|
-
matchName = 'section';
|
|
340
|
-
match = line.match(RE_SECTION);
|
|
341
|
-
}
|
|
342
|
-
if (match === null && action !== null) {
|
|
343
|
-
matchName = 'section_plain';
|
|
344
|
-
match = line.match(RE_SECTION_PLAIN);
|
|
345
|
-
}
|
|
346
|
-
if (match === null && userType !== null && 'enum' in userType) {
|
|
347
|
-
matchName = 'value';
|
|
348
|
-
match = line.match(RE_VALUE);
|
|
349
|
-
if (match === null) {
|
|
350
|
-
match = line.match(RE_VALUE_QUOTED);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
if (match === null && userType !== null && 'struct' in userType) {
|
|
354
|
-
matchName = 'member';
|
|
355
|
-
match = line.match(RE_MEMBER);
|
|
356
|
-
}
|
|
357
|
-
if (match === null && urls !== null) {
|
|
358
|
-
matchName = 'urls';
|
|
359
|
-
match = line.match(RE_URL);
|
|
360
|
-
}
|
|
361
|
-
if (match === null) {
|
|
362
|
-
matchName = 'typedef';
|
|
363
|
-
match = line.match(RE_TYPEDEF);
|
|
364
|
-
}
|
|
365
|
-
if (match === null) {
|
|
366
|
-
matchName = null;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// Comment?
|
|
370
|
-
if (matchName === 'comment') {
|
|
371
|
-
const docString = match.groups.doc;
|
|
372
|
-
if (typeof docString !== 'undefined') {
|
|
373
|
-
doc.push(!docString.startsWith(' ') ? docString : docString.slice(1));
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// Documentation group?
|
|
377
|
-
} else if (matchName === 'group') {
|
|
378
|
-
docGroup = match.groups.group;
|
|
379
|
-
if (typeof docGroup !== 'undefined') {
|
|
380
|
-
docGroup = docGroup.trim();
|
|
381
|
-
} else {
|
|
382
|
-
docGroup = null;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// Action?
|
|
386
|
-
} else if (matchName === 'action') {
|
|
387
|
-
const actionId = match.groups.id;
|
|
388
|
-
|
|
389
|
-
// Action already defined?
|
|
390
|
-
if (actionId in this.types) {
|
|
391
|
-
this.addError(`Redefinition of action '${actionId}'`, filename, linenum);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// Clear parser state
|
|
395
|
-
urls = null;
|
|
396
|
-
userType = null;
|
|
397
|
-
const actionDoc = getDoc();
|
|
398
|
-
|
|
399
|
-
// Create the new action
|
|
400
|
-
action = {'name': actionId};
|
|
401
|
-
this.types[actionId] = {'action': action};
|
|
402
|
-
if (actionDoc !== null) {
|
|
403
|
-
action.doc = actionDoc;
|
|
404
|
-
}
|
|
405
|
-
if (docGroup !== null) {
|
|
406
|
-
action.docGroup = docGroup;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// Definition?
|
|
410
|
-
} else if (matchName === 'definition') {
|
|
411
|
-
const definitionString = match.groups.type;
|
|
412
|
-
const definitionId = match.groups.id;
|
|
413
|
-
const definitionBaseIds = match.groups.base_ids;
|
|
414
|
-
|
|
415
|
-
// Type already defined?
|
|
416
|
-
if (BUILTIN_TYPES.has(definitionId) || definitionId in this.types) {
|
|
417
|
-
this.addError(`Redefinition of type '${definitionId}'`, filename, linenum);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// Clear parser state
|
|
421
|
-
action = null;
|
|
422
|
-
urls = null;
|
|
423
|
-
const definitionDoc = getDoc();
|
|
424
|
-
|
|
425
|
-
// Struct definition
|
|
426
|
-
if (definitionString === 'struct' || definitionString === 'union') {
|
|
427
|
-
// Create the new struct type
|
|
428
|
-
const struct = {'name': definitionId};
|
|
429
|
-
userType = {'struct': struct};
|
|
430
|
-
this.types[definitionId] = userType;
|
|
431
|
-
if (definitionDoc !== null) {
|
|
432
|
-
struct.doc = definitionDoc;
|
|
433
|
-
}
|
|
434
|
-
if (docGroup !== null) {
|
|
435
|
-
struct.docGroup = docGroup;
|
|
436
|
-
}
|
|
437
|
-
if (definitionString === 'union') {
|
|
438
|
-
struct.union = true;
|
|
439
|
-
}
|
|
440
|
-
if (typeof definitionBaseIds !== 'undefined') {
|
|
441
|
-
struct.bases = definitionBaseIds.split(RE_BASE_IDS_SPLIT);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
// Enum definition
|
|
445
|
-
} else {
|
|
446
|
-
// definition_string == 'enum':
|
|
447
|
-
// Create the new enum type
|
|
448
|
-
const enum_ = {'name': definitionId};
|
|
449
|
-
userType = {'enum': enum_};
|
|
450
|
-
this.types[definitionId] = userType;
|
|
451
|
-
if (definitionDoc !== null) {
|
|
452
|
-
enum_.doc = definitionDoc;
|
|
453
|
-
}
|
|
454
|
-
if (docGroup !== null) {
|
|
455
|
-
enum_.docGroup = docGroup;
|
|
456
|
-
}
|
|
457
|
-
if (typeof definitionBaseIds !== 'undefined') {
|
|
458
|
-
enum_.bases = definitionBaseIds.split(RE_BASE_IDS_SPLIT);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// Record finalization information
|
|
463
|
-
this.filepos[definitionId] = [filename, linenum];
|
|
464
|
-
|
|
465
|
-
// Action section?
|
|
466
|
-
} else if (matchName === 'section') {
|
|
467
|
-
const sectionString = match.groups.type;
|
|
468
|
-
const sectionBaseIds = match.groups.base_ids;
|
|
469
|
-
|
|
470
|
-
// Action section redefinition?
|
|
471
|
-
if (sectionString in action) {
|
|
472
|
-
this.addError(`Redefinition of action ${sectionString}`, filename, linenum);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// Clear parser state
|
|
476
|
-
urls = null;
|
|
477
|
-
|
|
478
|
-
// Set the action section type
|
|
479
|
-
const sectionTypeName = `${action.name}_${sectionString}`;
|
|
480
|
-
action[sectionString] = sectionTypeName;
|
|
481
|
-
if (sectionString === 'errors') {
|
|
482
|
-
const enum_ = {'name': sectionTypeName};
|
|
483
|
-
userType = {'enum': enum_};
|
|
484
|
-
this.types[sectionTypeName] = userType;
|
|
485
|
-
if (typeof sectionBaseIds !== 'undefined') {
|
|
486
|
-
enum_.bases = sectionBaseIds.split(RE_BASE_IDS_SPLIT);
|
|
487
|
-
}
|
|
488
|
-
} else {
|
|
489
|
-
const struct = {'name': sectionTypeName};
|
|
490
|
-
userType = {'struct': struct};
|
|
491
|
-
this.types[sectionTypeName] = userType;
|
|
492
|
-
if (typeof sectionBaseIds !== 'undefined') {
|
|
493
|
-
struct.bases = sectionBaseIds.split(RE_BASE_IDS_SPLIT);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
// Record finalization information
|
|
498
|
-
this.filepos[sectionTypeName] = [filename, linenum];
|
|
499
|
-
|
|
500
|
-
// Plain action section?
|
|
501
|
-
} else if (matchName === 'section_plain') {
|
|
502
|
-
const sectionString = match.groups.type;
|
|
503
|
-
|
|
504
|
-
// Action section redefinition?
|
|
505
|
-
if (sectionString in action) {
|
|
506
|
-
this.addError(`Redefinition of action ${sectionString}`, filename, linenum);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
// Clear parser state
|
|
510
|
-
userType = null;
|
|
511
|
-
|
|
512
|
-
// Update the parser state
|
|
513
|
-
urls = [];
|
|
514
|
-
|
|
515
|
-
// Enum value?
|
|
516
|
-
} else if (matchName === 'value') {
|
|
517
|
-
const valueString = match.groups.id;
|
|
518
|
-
|
|
519
|
-
// Add the enum value
|
|
520
|
-
const enum_ = userType.enum;
|
|
521
|
-
if (!('values' in enum_)) {
|
|
522
|
-
enum_.values = [];
|
|
523
|
-
}
|
|
524
|
-
const enumValue = {'name': valueString};
|
|
525
|
-
enum_.values.push(enumValue);
|
|
526
|
-
const enumValueDoc = getDoc();
|
|
527
|
-
if (enumValueDoc !== null) {
|
|
528
|
-
enumValue.doc = enumValueDoc;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
// Record finalization information
|
|
532
|
-
this.filepos[`${enum_.name}.${valueString}`] = [filename, linenum];
|
|
533
|
-
|
|
534
|
-
// Struct member?
|
|
535
|
-
} else if (matchName === 'member') {
|
|
536
|
-
const optional = typeof match.groups.optional !== 'undefined';
|
|
537
|
-
const memberName = match.groups.id;
|
|
538
|
-
|
|
539
|
-
// Add the member
|
|
540
|
-
const {struct} = userType;
|
|
541
|
-
if (!('members' in struct)) {
|
|
542
|
-
struct.members = [];
|
|
543
|
-
}
|
|
544
|
-
const [memberType, memberAttr] = parseTypedef(match);
|
|
545
|
-
const memberDoc = getDoc();
|
|
546
|
-
const member = {
|
|
547
|
-
'name': memberName,
|
|
548
|
-
'type': memberType
|
|
549
|
-
};
|
|
550
|
-
struct.members.push(member);
|
|
551
|
-
if (memberAttr !== null) {
|
|
552
|
-
member.attr = memberAttr;
|
|
553
|
-
}
|
|
554
|
-
if (memberDoc !== null) {
|
|
555
|
-
member.doc = memberDoc;
|
|
556
|
-
}
|
|
557
|
-
if (optional) {
|
|
558
|
-
member.optional = true;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
// Record finalization information
|
|
562
|
-
this.filepos[`${struct.name}.${memberName}`] = [filename, linenum];
|
|
563
|
-
|
|
564
|
-
// URL?
|
|
565
|
-
} else if (matchName === 'urls') {
|
|
566
|
-
const {method, path} = match.groups;
|
|
567
|
-
|
|
568
|
-
// Create the action URL object
|
|
569
|
-
const actionUrl = {};
|
|
570
|
-
if (method !== '*') {
|
|
571
|
-
actionUrl.method = method;
|
|
572
|
-
}
|
|
573
|
-
if (typeof path !== 'undefined') {
|
|
574
|
-
actionUrl.path = path;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
// Duplicate URL?
|
|
578
|
-
if (urls.some((url) => url.method === actionUrl.method && url.path === actionUrl.path)) {
|
|
579
|
-
this.addError(`Duplicate URL: ${method} ${'path' in actionUrl ? actionUrl.path : ''}`, filename, linenum);
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
// Add the URL
|
|
583
|
-
if (!('urls' in action)) {
|
|
584
|
-
action.urls = urls;
|
|
585
|
-
}
|
|
586
|
-
urls.push(actionUrl);
|
|
587
|
-
|
|
588
|
-
// Typedef?
|
|
589
|
-
} else if (matchName === 'typedef') {
|
|
590
|
-
const definitionId = match.groups.id;
|
|
591
|
-
|
|
592
|
-
// Type already defined?
|
|
593
|
-
if (BUILTIN_TYPES.has(definitionId) || definitionId in this.types) {
|
|
594
|
-
this.addError(`Redefinition of type '${definitionId}'`, filename, linenum);
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
// Clear parser state
|
|
598
|
-
action = null;
|
|
599
|
-
urls = null;
|
|
600
|
-
userType = null;
|
|
601
|
-
const typedefDoc = getDoc();
|
|
602
|
-
|
|
603
|
-
// Create the typedef
|
|
604
|
-
const [typedefType, typedefAttr] = parseTypedef(match);
|
|
605
|
-
const typedef = {
|
|
606
|
-
'name': definitionId,
|
|
607
|
-
'type': typedefType
|
|
608
|
-
};
|
|
609
|
-
this.types[definitionId] = {'typedef': typedef};
|
|
610
|
-
if (typedefAttr !== null) {
|
|
611
|
-
typedef.attr = typedefAttr;
|
|
612
|
-
}
|
|
613
|
-
if (typedefDoc !== null) {
|
|
614
|
-
typedef.doc = typedefDoc;
|
|
615
|
-
}
|
|
616
|
-
if (docGroup !== null) {
|
|
617
|
-
typedef.docGroup = docGroup;
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
// Record finalization information
|
|
621
|
-
this.filepos[definitionId] = [filename, linenum];
|
|
622
|
-
|
|
623
|
-
// Unrecognized line syntax
|
|
624
|
-
} else {
|
|
625
|
-
this.addError('Syntax error', filename, linenum);
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
// Finalize, if requested
|
|
631
|
-
if (finalize) {
|
|
632
|
-
this.finalize();
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
}
|