Skip to content

Commit

Permalink
Add support for structured headers for SDOs (#358)
Browse files Browse the repository at this point in the history
  • Loading branch information
bakkot authored Sep 14, 2021
1 parent 0da5baf commit 4786953
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 10 deletions.
9 changes: 7 additions & 2 deletions src/Clause.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ export default class Clause extends Builder {
// if we find such a DL, treat this as a structured header

const type = this.node.getAttribute('type');
this.node.removeAttribute('type'); // TODO maybe leave it in; this is just to minimize the diff

const { name, formattedHeader, formattedParams } = parseStructuredHeaderH1(this.spec, header);
if (type === 'numeric method' && name != null && !name.includes('::')) {
Expand All @@ -101,7 +100,11 @@ export default class Clause extends Builder {
nodeRelativeColumn: 1,
});
}
if (formattedHeader != null) {
if (type === 'sdo' && (formattedHeader ?? header.innerHTML).includes('(')) {
// SDOs are rendered without parameter lists in the header, for the moment
const currentHeader = formattedHeader ?? header.innerHTML;
header.innerHTML = currentHeader.substring(0, currentHeader.indexOf('(')).trim();
} else if (formattedHeader != null) {
header.innerHTML = formattedHeader;
}

Expand Down Expand Up @@ -132,6 +135,8 @@ export default class Clause extends Builder {
type != null &&
[
'abstract operation',
'sdo',
'syntax-directed operation',
'host-defined abstract operation',
'implementation-defined abstract operation',
'numeric method',
Expand Down
4 changes: 3 additions & 1 deletion src/Spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1235,7 +1235,9 @@ ${await utils.readFile(path.join(__dirname, '../js/multipage.js'))}
}
}

const sdos = this.doc.querySelectorAll('emu-clause[type=sdo]');
const sdos = this.doc.querySelectorAll(
'emu-clause[type=sdo],emu-clause[type="syntax-directed operation"]'
);
outer: for (const sdo of sdos) {
let header: Element | undefined;
for (const child of sdo.children) {
Expand Down
28 changes: 22 additions & 6 deletions src/header-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ export function parseStructuredHeaderH1(
headerText = headerText.substring(prefix[0].length);
}

const parsed = headerText.match(/^(?<beforeParams>\s*(?<name>[^(\s]+)\s*\()(?<params>.*)\)\s*$/s);
const parsed = headerText.match(
/^(?<beforeParams>\s*(?<name>[^(\s]+)\s*)(?:\((?<params>.*)\)\s*)?$/s
);
if (parsed == null) {
spec.warn({
type: 'contents',
Expand All @@ -28,7 +30,7 @@ export function parseStructuredHeaderH1(

type Param = { name: string; type: string | null };
const name = parsed.groups!.name;
let paramText = parsed.groups!.params;
let paramText = parsed.groups!.params ?? '';
const params: Array<Param> = [];
const optionalParams: Array<Param> = [];
let formattedHeader = null;
Expand All @@ -49,6 +51,7 @@ export function parseStructuredHeaderH1(
return (
(prefix?.[0].length ?? 0) +
parsed!.groups!.beforeParams.length +
1 + // `beforeParams` does not include the leading `(`
(offset - line.length) + // we've already updated offset to include line.length at this point
index + // to account for the `\n`s eaten by the .split
line.match(/^\s*/)![0].length
Expand Down Expand Up @@ -265,7 +268,8 @@ export function formatPreamble(
): Array<Element> {
const para = spec.doc.createElement('p');
const paras = [para];
switch (type?.toLowerCase()) {
type = (type ?? '').toLowerCase();
switch (type) {
case 'numeric method':
case 'abstract operation': {
// TODO tests (for each type of parametered thing) which have HTML in the parameter type
Expand All @@ -280,6 +284,11 @@ export function formatPreamble(
para.innerHTML += `The implementation-defined abstract operation ${name} takes ${formattedParams}.`;
break;
}
case 'sdo':
case 'syntax-directed operation': {
para.innerHTML += `The syntax-directed operation ${name} takes ${formattedParams}.`;
break;
}
case 'internal method':
case 'concrete method': {
if (_for == null) {
Expand Down Expand Up @@ -326,23 +335,30 @@ export function formatPreamble(
para.append(' ', ...description.childNodes);
}
}
const isSdo = type === 'sdo' || type === 'syntax-directed operation';
const lastSentence = isSdo
? 'It is defined piecewise over the following productions:'
: 'It performs the following steps when called:';
let next = dl.nextElementSibling;
while (next != null && next.tagName === 'EMU-NOTE') {
next = next.nextElementSibling;
}
if (next?.tagName == 'EMU-ALG' && !next.hasAttribute('replaces-step')) {
if (
(isSdo && next?.tagName === 'EMU-GRAMMAR') ||
(!isSdo && next?.tagName === 'EMU-ALG' && !next.hasAttribute('replaces-step'))
) {
if (paras.length > 1 || next !== dl.nextElementSibling) {
const whitespace = next.previousSibling;
const after = spec.doc.createElement('p');
after.append('It performs the following steps when called:');
after.append(lastSentence);
next.parentElement!.insertBefore(after, next);

// fix up the whitespace in the generated HTML
if (whitespace?.nodeType === 3 /* TEXT_NODE */ && /^\s+$/.test(whitespace.nodeValue!)) {
next.parentElement!.insertBefore(whitespace.cloneNode(), next);
}
} else {
para.append(' It performs the following steps when called:');
para.append(' ' + lastSentence);
}
}
return paras;
Expand Down
39 changes: 38 additions & 1 deletion test/baselines/generated-reference/structured-headers.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!doctype html>
<head><meta charset="utf-8"></head><body><div id="spec-container">

<emu-clause id="sec-exampleao" aoid="ExampleAO">
<emu-clause id="sec-exampleao" type="abstract operation" aoid="ExampleAO">
<h1><span class="secnum">1</span> ExampleAO ( param [ , param2 ] )</h1>
<p>The abstract operation ExampleAO takes argument param (an <emu-xref href="#integer"><a href="https://tc39.es/ecma262/#integer">integer</a></emu-xref>) and optional argument param2 (a String). It is an example.</p>
<emu-note><span class="note">Note</span><div class="note-contents">
Expand All @@ -10,4 +10,41 @@ <h1><span class="secnum">1</span> ExampleAO ( param [ , param2 ] )</h1>
<p>It performs the following steps when called:</p>
<emu-alg><ol><li>Algorithm steps go here.</li></ol></emu-alg>
</emu-clause>

<emu-clause id="sec-example-grammar">
<h1><span class="secnum">2</span> This and That</h1>
<emu-grammar type="definition"><emu-production name="PrimaryExpression" id="prod-PrimaryExpression">
<emu-nt><a href="#prod-PrimaryExpression">PrimaryExpression</a></emu-nt> <emu-geq>:</emu-geq> <emu-rhs a="jo4mwtvh" id="prod-Joc9-5i9"><emu-t>this</emu-t></emu-rhs>
<emu-rhs a="_ditw-4g" id="prod-QC7Z2SC4"><emu-t>that</emu-t></emu-rhs>
</emu-production>
</emu-grammar>
<emu-clause id="sec-isthis" type="sdo" aoid="IsThis">
<h1><span class="secnum">2.1</span> IsThis</h1>
<p>The <emu-xref href="#sec-algorithm-conventions-syntax-directed-operations"><a href="https://tc39.es/ecma262/#sec-algorithm-conventions-syntax-directed-operations">syntax-directed operation</a></emu-xref> IsThis takes no arguments. It is an example. It is defined piecewise over the following productions:</p>
<emu-grammar><emu-production name="PrimaryExpression" collapsed="">
<emu-nt><a href="#prod-PrimaryExpression">PrimaryExpression</a></emu-nt> <emu-geq>:</emu-geq> <emu-rhs a="jo4mwtvh" id="prod-oDKnYE2s"><emu-t>this</emu-t></emu-rhs>
</emu-production>
</emu-grammar>
<emu-alg><ol><li>Return <emu-val>true</emu-val>.</li></ol></emu-alg>
<emu-grammar><emu-production name="PrimaryExpression" collapsed="">
<emu-nt><a href="#prod-PrimaryExpression">PrimaryExpression</a></emu-nt> <emu-geq>:</emu-geq> <emu-rhs a="_ditw-4g" id="prod-_G-zA61Z"><emu-t>that</emu-t></emu-rhs>
</emu-production>
</emu-grammar>
<emu-alg><ol><li>Return <emu-val>false</emu-val>.</li></ol></emu-alg>
</emu-clause>
<emu-clause id="sec-isthat" type="sdo" aoid="IsThat">
<h1><span class="secnum">2.2</span> IsThat</h1>
<p>The <emu-xref href="#sec-algorithm-conventions-syntax-directed-operations"><a href="https://tc39.es/ecma262/#sec-algorithm-conventions-syntax-directed-operations">syntax-directed operation</a></emu-xref> IsThat takes argument <var>ignored</var> (an example). It is defined piecewise over the following productions:</p>
<emu-grammar><emu-production name="PrimaryExpression" collapsed="">
<emu-nt><a href="#prod-PrimaryExpression">PrimaryExpression</a></emu-nt> <emu-geq>:</emu-geq> <emu-rhs a="jo4mwtvh" id="prod-QDgm4qgx"><emu-t>this</emu-t></emu-rhs>
</emu-production>
</emu-grammar>
<emu-alg><ol><li>Return <emu-val>false</emu-val>.</li></ol></emu-alg>
<emu-grammar><emu-production name="PrimaryExpression" collapsed="">
<emu-nt><a href="#prod-PrimaryExpression">PrimaryExpression</a></emu-nt> <emu-geq>:</emu-geq> <emu-rhs a="_ditw-4g" id="prod-6qiWXvFG"><emu-t>that</emu-t></emu-rhs>
</emu-production>
</emu-grammar>
<emu-alg><ol><li>Return <emu-val>true</emu-val>.</li></ol></emu-alg>
</emu-clause>
</emu-clause>
</div></body>
41 changes: 41 additions & 0 deletions test/baselines/sources/structured-headers.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,44 @@ <h1>
1. Algorithm steps go here.
</emu-alg>
</emu-clause>

<emu-clause id="sec-example-grammar">
<h1>This and That</h1>
<emu-grammar type="definition">
PrimaryExpression :
`this`
`that`
</emu-grammar>
<emu-clause id="sec-isthis" type="sdo">
<h1>IsThis</h1>
<dl class='header'>
<dt>description</dt>
<dd>It is an example.</dd>
</dl>
<emu-grammar>PrimaryExpression : `this`</emu-grammar>
<emu-alg>
1. Return *true*.
</emu-alg>
<emu-grammar>PrimaryExpression : `that`</emu-grammar>
<emu-alg>
1. Return *false*.
</emu-alg>
</emu-clause>
<emu-clause id="sec-isthat" type="sdo">
<h1>
IsThat (
_ignored_: an example,
)
</h1>
<dl class='header'>
</dl>
<emu-grammar>PrimaryExpression : `this`</emu-grammar>
<emu-alg>
1. Return *false*.
</emu-alg>
<emu-grammar>PrimaryExpression : `that`</emu-grammar>
<emu-alg>
1. Return *true*.
</emu-alg>
</emu-clause>
</emu-clause>

0 comments on commit 4786953

Please sign in to comment.