1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.formatPreamble = exports.parseStructuredHeaderDl = exports.parseStructuredHeaderH1 = void 0;
|
4 | const utils_1 = require("./utils");
|
5 | function parseStructuredHeaderH1(spec, header) {
|
6 |
|
7 |
|
8 | var _a, _b, _c, _d;
|
9 | let wrapper = null;
|
10 | let headerText = header.innerHTML;
|
11 | let beforeContents = 0;
|
12 | const headerWrapperMatch = headerText.match(/^(?<beforeContents>\s*<(?<tag>ins|del|mark)>)(?<contents>.*)<\/\k<tag>>\s*$/is);
|
13 | if (headerWrapperMatch != null) {
|
14 | wrapper = headerWrapperMatch.groups.tag;
|
15 | headerText = headerWrapperMatch.groups.contents;
|
16 | beforeContents = headerWrapperMatch.groups.beforeContents.length;
|
17 | }
|
18 | const prefix = headerText.match(/^\s*(Static|Runtime) Semantics:\s*/);
|
19 | if (prefix != null) {
|
20 | headerText = headerText.substring(prefix[0].length);
|
21 | }
|
22 | const parsed = headerText.match(/^(?<beforeParams>\s*(?<name>[^(\s]+)\s*)(?:\((?<params>[^)]*)\)(?:\s*:(?<returnType>.*))?\s*)?$/s);
|
23 | if (parsed == null) {
|
24 | spec.warn({
|
25 | type: 'contents',
|
26 | ruleId: 'header-format',
|
27 | message: `failed to parse header`,
|
28 | node: header,
|
29 | nodeRelativeColumn: 1,
|
30 | nodeRelativeLine: 1,
|
31 | });
|
32 | return { name: null, formattedHeader: null, formattedParams: null, formattedReturnType: null };
|
33 | }
|
34 | const name = parsed.groups.name;
|
35 | let paramText = (_a = parsed.groups.params) !== null && _a !== void 0 ? _a : '';
|
36 | const returnType = (_c = (_b = parsed.groups.returnType) === null || _b === void 0 ? void 0 : _b.trim()) !== null && _c !== void 0 ? _c : null;
|
37 | if (returnType === '') {
|
38 | const { column, line } = utils_1.offsetToLineAndColumn(header.innerHTML, beforeContents +
|
39 | ((_d = prefix === null || prefix === void 0 ? void 0 : prefix[0].length) !== null && _d !== void 0 ? _d : 0) +
|
40 | (headerText.length - headerText.match(/\s*$/)[0].length) -
|
41 | 1);
|
42 | spec.warn({
|
43 | type: 'contents',
|
44 | ruleId: 'header-format',
|
45 | message: `if a return type is given, it must not be empty`,
|
46 | node: header,
|
47 | nodeRelativeColumn: column,
|
48 | nodeRelativeLine: line,
|
49 | });
|
50 | }
|
51 | const params = [];
|
52 | const optionalParams = [];
|
53 | let formattedHeader = null;
|
54 | if (/\(\s*\n/.test(headerText)) {
|
55 |
|
56 | const paramLines = paramText.split('\n');
|
57 | let index = 0;
|
58 | let offset = 0;
|
59 | for (const line of paramLines) {
|
60 | offset += line.length;
|
61 | let chunk = line.trim();
|
62 | if (chunk === '') {
|
63 | continue;
|
64 | }
|
65 | const wrapperMatch = chunk.match(/^<(ins|del|mark)>(.*)<\/\1>$/i);
|
66 | let paramWrapper = null;
|
67 | if (wrapperMatch != null) {
|
68 | paramWrapper = wrapperMatch[1];
|
69 | chunk = wrapperMatch[2];
|
70 | }
|
71 | ++index;
|
72 | function getParameterOffset() {
|
73 | var _a;
|
74 | return (beforeContents +
|
75 | ((_a = prefix === null || prefix === void 0 ? void 0 : prefix[0].length) !== null && _a !== void 0 ? _a : 0) +
|
76 | parsed.groups.beforeParams.length +
|
77 | 1 +
|
78 | (offset - line.length) +
|
79 | index +
|
80 | line.match(/^\s*/)[0].length);
|
81 | }
|
82 | const parsedChunk = chunk.match(/^(optional\s+)?([A-Za-z0-9_]+)\s*:\s*(\S.*\S)/);
|
83 | if (parsedChunk == null) {
|
84 | const { line: nodeRelativeLine, column: nodeRelativeColumn } = utils_1.offsetToLineAndColumn(header.innerHTML, getParameterOffset());
|
85 | spec.warn({
|
86 | type: 'contents',
|
87 | ruleId: 'header-format',
|
88 | message: `failed to parse parameter ${index}`,
|
89 | node: header,
|
90 | nodeRelativeColumn,
|
91 | nodeRelativeLine,
|
92 | });
|
93 | continue;
|
94 | }
|
95 | const optional = parsedChunk[1] != null;
|
96 | const paramName = parsedChunk[2];
|
97 | let paramType = parsedChunk[3];
|
98 | if (paramType.endsWith(',')) {
|
99 | paramType = paramType.slice(0, -1);
|
100 | }
|
101 | if (!optional && optionalParams.length > 0) {
|
102 | const { line: nodeRelativeLine, column: nodeRelativeColumn } = utils_1.offsetToLineAndColumn(header.innerHTML, getParameterOffset());
|
103 | spec.warn({
|
104 | type: 'contents',
|
105 | ruleId: 'header-format',
|
106 | message: `required parameters should not follow optional parameters`,
|
107 | node: header,
|
108 | nodeRelativeColumn,
|
109 | nodeRelativeLine,
|
110 | });
|
111 | }
|
112 | (optional ? optionalParams : params).push({
|
113 | name: paramName,
|
114 | type: paramType === 'unknown' ? null : paramType,
|
115 | wrapper: paramWrapper,
|
116 | });
|
117 | }
|
118 | const formattedPrefix = prefix == null ? '' : prefix[0].trim() + ' ';
|
119 |
|
120 | const printParam = (p) => ` ${p.wrapper == null ? '' : `<${p.wrapper}>`}${p.name}${p.wrapper == null ? '' : `</${p.wrapper}>`}`;
|
121 | formattedHeader = `${formattedPrefix}${name} (${params.map(printParam).join(',')}`;
|
122 | if (optionalParams.length > 0) {
|
123 | formattedHeader +=
|
124 | optionalParams
|
125 | .map((p, i) => ' [ ' + (i > 0 || params.length > 0 ? ', ' : '') + p.name)
|
126 | .join('') + optionalParams.map(() => ' ]').join('');
|
127 | }
|
128 | formattedHeader += ' )';
|
129 | }
|
130 | else {
|
131 | let optional = false;
|
132 | paramText = paramText.trim();
|
133 | while (true) {
|
134 | if (paramText.length == 0) {
|
135 | break;
|
136 | }
|
137 |
|
138 | let { success, text, match } = eat(paramText, /^\s*\[(\s*,)?/);
|
139 | if (success) {
|
140 | optional = true;
|
141 | paramText = text;
|
142 | }
|
143 | ({ success, text, match } = eat(paramText, /^\s*([A-Za-z0-9_]+)/));
|
144 | if (!success) {
|
145 | spec.warn({
|
146 | type: 'contents',
|
147 | ruleId: 'header-format',
|
148 | message: `failed to parse header`,
|
149 | node: header,
|
150 |
|
151 | nodeRelativeLine: 1,
|
152 | nodeRelativeColumn: 1,
|
153 | });
|
154 | break;
|
155 | }
|
156 | paramText = text;
|
157 | (optional ? optionalParams : params).push({ name: match[1], type: null, wrapper: null });
|
158 | ({ success, text } = eat(paramText, /^(\s*\])+|,/));
|
159 | if (success) {
|
160 | paramText = text;
|
161 | }
|
162 | }
|
163 | }
|
164 |
|
165 | const printParamWithType = (p) => `${p.wrapper == null ? '' : `<${p.wrapper}>`}${p.name}${p.type == null ? '' : ` (${p.type})`}${p.wrapper == null ? '' : `</${p.wrapper}>`}`;
|
166 | const paramsWithTypes = params.map(printParamWithType);
|
167 | const optionalParamsWithTypes = optionalParams.map(printParamWithType);
|
168 | let formattedParams = '';
|
169 | if (params.length === 0 && optionalParams.length === 0) {
|
170 | formattedParams = 'no arguments';
|
171 | }
|
172 | else {
|
173 | if (params.length > 0) {
|
174 | formattedParams =
|
175 | (params.length === 1 ? 'argument' : 'arguments') + ' ' + formatEnglishList(paramsWithTypes);
|
176 | if (optionalParams.length > 0) {
|
177 | formattedParams += ' and ';
|
178 | }
|
179 | }
|
180 | if (optionalParams.length > 0) {
|
181 | formattedParams +=
|
182 | 'optional ' +
|
183 | (optionalParams.length === 1 ? 'argument' : 'arguments') +
|
184 | ' ' +
|
185 | formatEnglishList(optionalParamsWithTypes);
|
186 | }
|
187 | }
|
188 | if (formattedHeader != null && wrapper != null) {
|
189 | formattedHeader = `<${wrapper}>${formattedHeader}</${wrapper}>`;
|
190 | }
|
191 | return { name, formattedHeader, formattedParams, formattedReturnType: returnType };
|
192 | }
|
193 | exports.parseStructuredHeaderH1 = parseStructuredHeaderH1;
|
194 | function parseStructuredHeaderDl(spec, type, dl) {
|
195 | var _a;
|
196 | let description = null;
|
197 | let _for = null;
|
198 | let effects = [];
|
199 | for (let i = 0; i < dl.children.length; ++i) {
|
200 | const dt = dl.children[i];
|
201 | if (dt.tagName !== 'DT') {
|
202 | spec.warn({
|
203 | type: 'node',
|
204 | ruleId: 'header-format',
|
205 | message: `expecting header to have DT, but found ${dt.tagName}`,
|
206 | node: dt,
|
207 | });
|
208 | break;
|
209 | }
|
210 | ++i;
|
211 | const dd = dl.children[i];
|
212 | if ((dd === null || dd === void 0 ? void 0 : dd.tagName) !== 'DD') {
|
213 | spec.warn({
|
214 | type: 'node',
|
215 | ruleId: 'header-format',
|
216 | message: `expecting header to have DD, but found ${dd.tagName}`,
|
217 | node: dd,
|
218 | });
|
219 | break;
|
220 | }
|
221 | const dtype = (_a = dt.textContent) !== null && _a !== void 0 ? _a : '';
|
222 | switch (dtype.trim().toLowerCase()) {
|
223 | case 'description': {
|
224 | if (description != null) {
|
225 | spec.warn({
|
226 | type: 'node',
|
227 | ruleId: 'header-format',
|
228 | message: `duplicate "description" attribute`,
|
229 | node: dt,
|
230 | });
|
231 | }
|
232 | description = dd;
|
233 | break;
|
234 | }
|
235 | case 'for': {
|
236 | if (_for != null) {
|
237 | spec.warn({
|
238 | type: 'node',
|
239 | ruleId: 'header-format',
|
240 | message: `duplicate "for" attribute`,
|
241 | node: dt,
|
242 | });
|
243 | }
|
244 | if (type === 'concrete method' || type === 'internal method') {
|
245 | _for = dd;
|
246 | }
|
247 | else {
|
248 | spec.warn({
|
249 | type: 'node',
|
250 | ruleId: 'header-format',
|
251 | message: `"for" attributes only apply to concrete or internal methods`,
|
252 | node: dt,
|
253 | });
|
254 | }
|
255 | break;
|
256 | }
|
257 | case 'effects': {
|
258 |
|
259 | if (dd.textContent !== null) {
|
260 | effects = utils_1.validateEffects(spec, dd.textContent.split(',').map(c => c.trim()), dd);
|
261 | }
|
262 | break;
|
263 | }
|
264 | case '': {
|
265 | spec.warn({
|
266 | type: 'node',
|
267 | ruleId: 'header-format',
|
268 | message: `missing value for structured header attribute`,
|
269 | node: dt,
|
270 | });
|
271 | break;
|
272 | }
|
273 | default: {
|
274 | spec.warn({
|
275 | type: 'node',
|
276 | ruleId: 'header-format',
|
277 | message: `unknown structured header entry type ${JSON.stringify(dtype)}`,
|
278 | node: dt,
|
279 | });
|
280 | break;
|
281 | }
|
282 | }
|
283 | }
|
284 | return { description, for: _for, effects };
|
285 | }
|
286 | exports.parseStructuredHeaderDl = parseStructuredHeaderDl;
|
287 | function formatPreamble(spec, clause, dl, type, name, formattedParams, formattedReturnType, _for, description) {
|
288 | const para = spec.doc.createElement('p');
|
289 | const paras = [para];
|
290 | type = (type !== null && type !== void 0 ? type : '').toLowerCase();
|
291 | switch (type) {
|
292 | case 'numeric method':
|
293 | case 'abstract operation': {
|
294 |
|
295 | para.innerHTML += `The abstract operation ${name}`;
|
296 | break;
|
297 | }
|
298 | case 'host-defined abstract operation': {
|
299 | para.innerHTML += `The host-defined abstract operation ${name}`;
|
300 | break;
|
301 | }
|
302 | case 'implementation-defined abstract operation': {
|
303 | para.innerHTML += `The implementation-defined abstract operation ${name}`;
|
304 | break;
|
305 | }
|
306 | case 'sdo':
|
307 | case 'syntax-directed operation': {
|
308 | para.innerHTML += `The syntax-directed operation ${name}`;
|
309 | break;
|
310 | }
|
311 | case 'internal method':
|
312 | case 'concrete method': {
|
313 | if (_for == null) {
|
314 | spec.warn({
|
315 | type: 'contents',
|
316 | ruleId: 'header-format',
|
317 | message: `expected ${type} to have a "for"`,
|
318 | node: dl,
|
319 | nodeRelativeLine: 1,
|
320 | nodeRelativeColumn: 1,
|
321 | });
|
322 | _for = spec.doc.createElement('div');
|
323 | }
|
324 | para.append(`The ${name} ${type} of `, ..._for.childNodes);
|
325 | break;
|
326 | }
|
327 | default: {
|
328 | if (type) {
|
329 | spec.warn({
|
330 | type: 'attr-value',
|
331 | ruleId: 'header-type',
|
332 | message: `unknown clause type ${JSON.stringify(type)}`,
|
333 | node: clause,
|
334 | attr: 'type',
|
335 | });
|
336 | }
|
337 | else {
|
338 | spec.warn({
|
339 | type: 'node',
|
340 | ruleId: 'header-type',
|
341 | message: `clauses with structured headers should have a type`,
|
342 | node: clause,
|
343 | });
|
344 | }
|
345 | }
|
346 | }
|
347 | para.innerHTML += ` takes ${formattedParams}`;
|
348 | if (formattedReturnType != null) {
|
349 | para.innerHTML += ` and returns ${formattedReturnType}`;
|
350 | }
|
351 | para.innerHTML += '.';
|
352 | if (description != null) {
|
353 | const isJustElements = [...description.childNodes].every(n => { var _a; return n.nodeType === 1 || (n.nodeType === 3 && ((_a = n.textContent) === null || _a === void 0 ? void 0 : _a.trim()) === ''); });
|
354 | if (isJustElements) {
|
355 | paras.push(...description.childNodes);
|
356 | }
|
357 | else {
|
358 | para.append(' ', ...description.childNodes);
|
359 | }
|
360 | }
|
361 | const isSdo = type === 'sdo' || type === 'syntax-directed operation';
|
362 | const lastSentence = isSdo
|
363 | ? 'It is defined piecewise over the following productions:'
|
364 | : 'It performs the following steps when called:';
|
365 | let next = dl.nextElementSibling;
|
366 | while (next != null && next.tagName === 'EMU-NOTE') {
|
367 | next = next.nextElementSibling;
|
368 | }
|
369 | if ((isSdo && (next === null || next === void 0 ? void 0 : next.tagName) === 'EMU-GRAMMAR') ||
|
370 | (!isSdo && (next === null || next === void 0 ? void 0 : next.tagName) === 'EMU-ALG' && !next.hasAttribute('replaces-step'))) {
|
371 | if (paras.length > 1 || next !== dl.nextElementSibling) {
|
372 | const whitespace = next.previousSibling;
|
373 | const after = spec.doc.createElement('p');
|
374 | after.append(lastSentence);
|
375 | next.parentElement.insertBefore(after, next);
|
376 |
|
377 | if ((whitespace === null || whitespace === void 0 ? void 0 : whitespace.nodeType) === 3 && /^\s+$/.test(whitespace.nodeValue)) {
|
378 | next.parentElement.insertBefore(whitespace.cloneNode(), next);
|
379 | }
|
380 | }
|
381 | else {
|
382 | para.append(' ' + lastSentence);
|
383 | }
|
384 | }
|
385 | return paras;
|
386 | }
|
387 | exports.formatPreamble = formatPreamble;
|
388 | function eat(text, regex) {
|
389 | const match = text.match(regex);
|
390 | if (match == null) {
|
391 | return { success: false, match, text };
|
392 | }
|
393 | return { success: true, match, text: text.substring(match[0].length) };
|
394 | }
|
395 | function formatEnglishList(list) {
|
396 | if (list.length === 0) {
|
397 | throw new Error('formatEnglishList should not be called with an empty list');
|
398 | }
|
399 | if (list.length === 1) {
|
400 | return list[0];
|
401 | }
|
402 | if (list.length === 2) {
|
403 | return `${list[0]} and ${list[1]}`;
|
404 | }
|
405 | return `${list.slice(0, -1).join(', ')}, and ${list[list.length - 1]}`;
|
406 | }
|