UNPKG

11 kBJavaScriptView Raw
1"use strict";
2var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4 return new (P || (P = Promise))(function (resolve, reject) {
5 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7 function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8 step((generator = generator.apply(thisArg, _arguments || [])).next());
9 });
10};
11Object.defineProperty(exports, "__esModule", { value: true });
12const Builder_1 = require("./Builder");
13const header_parser_1 = require("./header-parser");
14/*@internal*/
15class Clause extends Builder_1.default {
16 constructor(spec, node, parent, number) {
17 super(spec, node);
18 this.parentClause = parent;
19 this.id = node.getAttribute('id');
20 this.number = number;
21 this.subclauses = [];
22 this.notes = [];
23 this.editorNotes = [];
24 this.examples = [];
25 this.effects = [];
26 // namespace is either the entire spec or the parent clause's namespace.
27 let parentNamespace = spec.namespace;
28 if (parent) {
29 parentNamespace = parent.namespace;
30 }
31 if (node.hasAttribute('namespace')) {
32 this.namespace = node.getAttribute('namespace');
33 spec.biblio.createNamespace(this.namespace, parentNamespace);
34 }
35 else {
36 this.namespace = parentNamespace;
37 }
38 this.aoid = node.getAttribute('aoid');
39 if (this.aoid === '') {
40 // <emu-clause id=foo aoid> === <emu-clause id=foo aoid=foo>
41 this.aoid = node.id;
42 }
43 this.type = node.getAttribute('type');
44 if (this.type === '') {
45 this.type = null;
46 }
47 let header = this.node.firstElementChild;
48 while (header != null && header.tagName === 'SPAN' && header.children.length === 0) {
49 // skip oldids
50 header = header.nextElementSibling;
51 }
52 if (header == null) {
53 this.spec.warn({
54 type: 'node',
55 ruleId: 'missing-header',
56 message: `could not locate header element`,
57 node: this.node,
58 });
59 header = null;
60 }
61 else if (header.tagName !== 'H1') {
62 this.spec.warn({
63 type: 'node',
64 ruleId: 'missing-header',
65 message: `could not locate header element; found <${header.tagName.toLowerCase()}> before any <h1>`,
66 node: header,
67 });
68 header = null;
69 }
70 else {
71 this.buildStructuredHeader(header);
72 }
73 this.header = header;
74 }
75 buildStructuredHeader(header) {
76 const dl = header.nextElementSibling;
77 if (dl == null || dl.tagName !== 'DL' || !dl.classList.contains('header')) {
78 return;
79 }
80 // if we find such a DL, treat this as a structured header
81 const type = this.type;
82 const { name, formattedHeader, formattedParams, formattedReturnType } = header_parser_1.parseStructuredHeaderH1(this.spec, header);
83 if (type === 'numeric method' && name != null && !name.includes('::')) {
84 this.spec.warn({
85 type: 'contents',
86 ruleId: 'numeric-method-for',
87 message: 'numeric methods should be of the form `Type::operation`',
88 node: header,
89 nodeRelativeLine: 1,
90 nodeRelativeColumn: 1,
91 });
92 }
93 if (type === 'sdo' && (formattedHeader !== null && formattedHeader !== void 0 ? formattedHeader : header.innerHTML).includes('(')) {
94 // SDOs are rendered without parameter lists in the header, for the moment
95 const currentHeader = formattedHeader !== null && formattedHeader !== void 0 ? formattedHeader : header.innerHTML;
96 header.innerHTML = (currentHeader.substring(0, currentHeader.indexOf('(')) +
97 currentHeader.substring(currentHeader.lastIndexOf(')') + 1)).trim();
98 if (header.children.length === 1 &&
99 ['INS', 'DEL', 'MARK'].includes(header.children[0].tagName)) {
100 header.children[0].innerHTML = header.children[0].innerHTML.trim();
101 }
102 }
103 else if (formattedHeader != null) {
104 header.innerHTML = formattedHeader;
105 }
106 const { description, for: _for, effects } = header_parser_1.parseStructuredHeaderDl(this.spec, type, dl);
107 const paras = header_parser_1.formatPreamble(this.spec, this.node, dl, type, name !== null && name !== void 0 ? name : 'UNKNOWN', formattedParams !== null && formattedParams !== void 0 ? formattedParams : 'UNPARSEABLE ARGUMENTS', formattedReturnType, _for, description);
108 dl.replaceWith(...paras);
109 if (this.node.hasAttribute('aoid')) {
110 this.spec.warn({
111 type: 'attr',
112 ruleId: 'header-format',
113 message: `nodes with structured headers should not include an AOID`,
114 node: this.node,
115 attr: 'aoid',
116 });
117 }
118 else if (name != null &&
119 type != null &&
120 [
121 'abstract operation',
122 'sdo',
123 'syntax-directed operation',
124 'host-defined abstract operation',
125 'implementation-defined abstract operation',
126 'numeric method',
127 ].includes(type)) {
128 this.node.setAttribute('aoid', name);
129 this.aoid = name;
130 }
131 this.effects = effects;
132 for (const effect of effects) {
133 if (!this.spec._effectWorklist.has(effect)) {
134 this.spec._effectWorklist.set(effect, []);
135 }
136 this.spec._effectWorklist.get(effect).push(this);
137 }
138 }
139 buildNotes() {
140 if (this.notes.length === 1) {
141 this.notes[0].build();
142 }
143 else {
144 // pass along note index
145 this.notes.forEach((note, i) => {
146 note.build(i + 1);
147 });
148 }
149 this.editorNotes.forEach(note => note.build());
150 }
151 buildExamples() {
152 if (this.examples.length === 1) {
153 this.examples[0].build();
154 }
155 else {
156 // pass along example index
157 this.examples.forEach((example, i) => {
158 example.build(i + 1);
159 });
160 }
161 }
162 canHaveEffect(effectName) {
163 // The following effects are runtime only:
164 //
165 // user-code: Only runtime can call user code.
166 if (this.title !== null && this.title.startsWith('Static Semantics:')) {
167 if (effectName === 'user-code')
168 return false;
169 }
170 return true;
171 }
172 static enter({ spec, node, clauseStack, clauseNumberer }) {
173 return __awaiter(this, void 0, void 0, function* () {
174 if (!node.id) {
175 spec.warn({
176 type: 'node',
177 ruleId: 'missing-id',
178 message: "clause doesn't have an id",
179 node,
180 });
181 }
182 let nextNumber = '';
183 if (node.nodeName !== 'EMU-INTRO') {
184 nextNumber = clauseNumberer.next(clauseStack.length, node.nodeName === 'EMU-ANNEX').value;
185 }
186 const parent = clauseStack[clauseStack.length - 1] || null;
187 const clause = new Clause(spec, node, parent, nextNumber);
188 if (parent) {
189 parent.subclauses.push(clause);
190 }
191 else {
192 spec.subclauses.push(clause);
193 }
194 clauseStack.push(clause);
195 });
196 }
197 static exit({ node, spec, clauseStack, inAlg, currentId }) {
198 const clause = clauseStack[clauseStack.length - 1];
199 const header = clause.header;
200 if (header == null) {
201 clause.title = 'UNKNOWN';
202 clause.titleHTML = 'UNKNOWN';
203 }
204 else {
205 const headerClone = header.cloneNode(true);
206 for (const a of headerClone.querySelectorAll('a')) {
207 a.replaceWith(...a.childNodes);
208 }
209 clause.titleHTML = headerClone.innerHTML;
210 clause.title = headerClone.textContent;
211 if (clause.number) {
212 const numElem = clause.spec.doc.createElement('span');
213 numElem.setAttribute('class', 'secnum');
214 numElem.textContent = clause.number;
215 header.insertBefore(clause.spec.doc.createTextNode(' '), header.firstChild);
216 header.insertBefore(numElem, header.firstChild);
217 }
218 }
219 clause.buildExamples();
220 clause.buildNotes();
221 const attributes = [];
222 if (node.hasAttribute('normative-optional')) {
223 attributes.push('Normative Optional');
224 }
225 if (node.hasAttribute('legacy')) {
226 attributes.push('Legacy');
227 }
228 if (attributes.length > 0) {
229 const tag = spec.doc.createElement('div');
230 tag.className = 'clause-attributes-tag';
231 const text = attributes.join(', ');
232 const contents = spec.doc.createTextNode(text);
233 tag.append(contents);
234 node.prepend(tag);
235 // we've already walked past the text node, so it won't get picked up by the usual process for autolinking
236 spec._textNodes[clause.namespace] = spec._textNodes[clause.namespace] || [];
237 spec._textNodes[clause.namespace].push({
238 node: contents,
239 clause,
240 inAlg,
241 currentId,
242 });
243 }
244 // clauses are always at the spec-level namespace.
245 const entry = {
246 type: 'clause',
247 id: clause.id,
248 aoid: clause.aoid,
249 title: clause.title,
250 titleHTML: clause.titleHTML,
251 number: clause.number,
252 };
253 if (clause.aoid) {
254 const existing = spec.biblio.keysForNamespace(spec.namespace);
255 if (existing.has(clause.aoid)) {
256 spec.warn({
257 type: 'node',
258 node,
259 ruleId: 'duplicate-definition',
260 message: `duplicate definition ${JSON.stringify(clause.aoid)}`,
261 });
262 }
263 }
264 spec.biblio.add(entry, spec.namespace);
265 clauseStack.pop();
266 }
267}
268exports.default = Clause;
269Clause.elements = ['EMU-INTRO', 'EMU-CLAUSE', 'EMU-ANNEX'];
270Clause.linkElements = Clause.elements;