-
Notifications
You must be signed in to change notification settings - Fork 65
/
Copy pathAlgorithm.ts
119 lines (106 loc) · 3.89 KB
/
Algorithm.ts
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
import type { Context } from './Context';
import type { Node as EcmarkdownNode, OrderedListItemNode } from 'ecmarkdown';
import type { PartialBiblioEntry, StepBiblioEntry } from './Biblio';
import Builder from './Builder';
import { warnEmdFailure, wrapEmdFailure } from './utils';
import { collectNonterminalsFromEmd } from './lint/utils';
import * as emd from 'ecmarkdown';
function findLabeledSteps(root: EcmarkdownNode) {
const steps: OrderedListItemNode[] = [];
emd.visit(root, {
enter(node: EcmarkdownNode) {
if (node.name === 'ordered-list-item' && node.id != null) {
steps.push(node);
}
},
});
return steps;
}
/*@internal*/
export default class Algorithm extends Builder {
static async enter(context: Context) {
context.inAlg = true;
const { spec, node, clauseStack } = context;
const innerHTML = node.innerHTML; // TODO use original slice, forward this from linter
let emdTree;
if ('ecmarkdownTree' in node) {
emdTree = (node as any).ecmarkdownTree;
} else {
try {
emdTree = emd.parseAlgorithm(innerHTML);
} catch (e) {
warnEmdFailure(spec.warn, node, e);
}
}
if (emdTree == null) {
node.innerHTML = wrapEmdFailure(innerHTML);
return;
}
if (spec.opts.lintSpec && spec.locate(node) != null && !node.hasAttribute('example')) {
const clause = clauseStack[clauseStack.length - 1];
const namespace = clause ? clause.namespace : spec.namespace;
const nonterminals = collectNonterminalsFromEmd(emdTree).map(({ name, loc }) => ({
name,
loc,
node,
namespace,
}));
spec._ntStringRefs = spec._ntStringRefs.concat(nonterminals);
}
const rawHtml = emd.emit(emdTree);
// replace spaces after !/? with to prevent bad line breaking
const html = rawHtml.replace(/((?:\s+|>)[!?])[ \t]+/g, '$1 ');
node.innerHTML = html;
const labeledStepEntries: StepBiblioEntry[] = [];
const replaces = node.getAttribute('replaces-step');
if (replaces) {
context.spec.replacementAlgorithms.push({
element: node,
target: replaces,
});
context.spec.replacementAlgorithmToContainedLabeledStepEntries.set(node, labeledStepEntries);
}
if (replaces && node.firstElementChild!.children.length > 1) {
const labeledSteps = findLabeledSteps(emdTree);
for (const step of labeledSteps) {
const itemSource = innerHTML.slice(step.location.start.offset, step.location.end.offset);
const offset = itemSource.match(/^\s*\d+\. \[id="/)![0].length;
spec.warn({
type: 'contents',
ruleId: 'labeled-step-in-replacement',
message:
'labeling a step in a replacement algorithm which has multiple top-level steps is unsupported because the resulting step number would be ambiguous',
node,
nodeRelativeLine: step.location.start.line,
nodeRelativeColumn: step.location.start.column + offset,
});
}
}
for (const step of node.querySelectorAll('li[id]')) {
const entry: PartialBiblioEntry = {
type: 'step',
id: step.id,
stepNumbers: getStepNumbers(step as Element),
};
context.spec.biblio.add(entry);
if (replaces) {
// The biblio entries for labeled steps in replacement algorithms will be modified in-place by a subsequent pass
labeledStepEntries.push(entry as StepBiblioEntry);
context.spec.labeledStepsToBeRectified.add(step.id);
}
}
}
static exit(context: Context) {
context.inAlg = false;
}
static elements = ['EMU-ALG'];
}
function getStepNumbers(item: Element) {
const { indexOf } = Array.prototype;
const counts = [];
while (item.parentElement?.tagName === 'OL') {
counts.unshift(1 + indexOf.call(item.parentElement.children, item));
item = item.parentElement.parentElement!;
}
return counts;
}