Skip to content

Commit

Permalink
Ensure emu-meta tags are fenced by the same mechanisms that xrefs are (
Browse files Browse the repository at this point in the history
  • Loading branch information
bakkot authored Dec 16, 2021
1 parent 7f9c84c commit da3f316
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 59 deletions.
24 changes: 4 additions & 20 deletions src/Clause.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ export default class Clause extends Builder {
this.buildStructuredHeader(header);
}
this.header = header;
if (header == null) {
this.title = 'UNKNOWN';
this.titleHTML = 'UNKNOWN';
}
}

buildStructuredHeader(header: Element) {
Expand Down Expand Up @@ -240,26 +244,6 @@ export default class Clause extends Builder {
static exit({ node, spec, clauseStack, inAlg, currentId }: Context) {
const clause = clauseStack[clauseStack.length - 1];

const header = clause.header;
if (header == null) {
clause.title = 'UNKNOWN';
clause.titleHTML = 'UNKNOWN';
} else {
const headerClone = header.cloneNode(true) as Element;
for (const a of headerClone.querySelectorAll('a')) {
a.replaceWith(...a.childNodes);
}
clause.titleHTML = headerClone.innerHTML;
clause.title = headerClone.textContent;
if (clause.number) {
const numElem = clause.spec.doc.createElement('span');
numElem.setAttribute('class', 'secnum');
numElem.textContent = clause.number;
header.insertBefore(clause.spec.doc.createTextNode(' '), header.firstChild);
header.insertBefore(numElem, header.firstChild);
}
}

clause.buildExamples();
clause.buildNotes();

Expand Down
30 changes: 30 additions & 0 deletions src/H1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Builder from './Builder';
import type { Context } from './Context';

export default class H1 extends Builder {
static elements = ['H1'];

static async enter() {
// do nothing
}

static async exit({ spec, node, clauseStack }: Context) {
const parent = clauseStack[clauseStack.length - 1] || null;
if (parent === null || parent.header !== node) {
return;
}
const headerClone = node.cloneNode(true) as Element;
for (const a of headerClone.querySelectorAll('a')) {
a.replaceWith(...a.childNodes);
}
parent.titleHTML = headerClone.innerHTML;
parent.title = headerClone.textContent;
if (parent.number) {
const numElem = spec.doc.createElement('span');
numElem.setAttribute('class', 'secnum');
numElem.textContent = parent.number;
node.insertBefore(spec.doc.createTextNode(' '), node.firstChild);
node.insertBefore(numElem, node.firstChild);
}
}
}
15 changes: 8 additions & 7 deletions src/Meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import type { Context } from './Context';

import Builder from './Builder';

import { validateEffects } from './utils';
import { validateEffects, doesEffectPropagateToParent } from './utils';
import { maybeAddClauseToEffectWorklist } from './Spec';

export default class Meta extends Builder {
static elements = ['EMU-META'];
Expand All @@ -20,13 +21,13 @@ export default class Meta extends Builder {
node
);
for (const effect of effects) {
if (!parent.effects.includes(effect)) {
parent.effects.push(effect);
if (!spec._effectWorklist.has(effect)) {
spec._effectWorklist.set(effect, []);
}
spec._effectWorklist.get(effect)!.push(parent);
if (!doesEffectPropagateToParent(node, effect)) {
continue;
}
if (!spec._effectWorklist.has(effect)) {
spec._effectWorklist.set(effect, []);
}
maybeAddClauseToEffectWorklist(effect, parent, spec._effectWorklist.get(effect)!);
}
}
spec._emuMetasToRender.add(node);
Expand Down
8 changes: 7 additions & 1 deletion src/Spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import Xref from './Xref';
import Eqn from './Eqn';
import Biblio from './Biblio';
import Meta from './Meta';
import H1 from './H1';
import {
autolink,
replacerForNamespace,
Expand Down Expand Up @@ -78,6 +79,7 @@ const builders: BuilderInterface[] = [
ProdRef,
Note,
Meta,
H1,
];

const visitorMap = builders.reduce((map, T) => {
Expand Down Expand Up @@ -258,7 +260,11 @@ function isEmuImportElement(node: Node): node is EmuImportElement {
return node.nodeType === 1 && node.nodeName === 'EMU-IMPORT';
}

function maybeAddClauseToEffectWorklist(effectName: string, clause: Clause, worklist: Clause[]) {
export function maybeAddClauseToEffectWorklist(
effectName: string,
clause: Clause,
worklist: Clause[]
) {
if (
!worklist.includes(clause) &&
clause.canHaveEffect(effectName) &&
Expand Down
34 changes: 4 additions & 30 deletions src/Xref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type * as Biblio from './Biblio';
import type Clause from './Clause';

import Builder from './Builder';
import { validateEffects } from './utils';
import { validateEffects, doesEffectPropagateToParent } from './utils';

/*@internal*/
export default class Xref extends Builder {
Expand Down Expand Up @@ -91,36 +91,10 @@ export default class Xref extends Builder {
shouldPropagateEffect(effectName: string) {
if (!this.isInvocation) return false;
if (this.clause) {
// Xrefs should not propagate past explicit fences in parent steps. Fences
// must be at the beginning of steps.
//
// Abstract Closures are considered automatic fences for the user-code
// effect, since those are effectively nested functions.
//
// Calls to Abstract Closures that can call user code must be explicitly
// marked as such with <emu-meta effects="user-code">...</emu-meta>.
for (let node = this.node; node.parentElement; node = node.parentElement) {
const parent = node.parentElement;
// This is super hacky. It's checking the output of ecmarkdown.
if (parent.tagName !== 'LI') continue;

if (
effectName === 'user-code' &&
parent.textContent?.includes('be a new Abstract Closure')
) {
return false;
}

if (
parent
.getAttribute('fence-effects')
?.split(',')
.map(s => s.trim())
.includes(effectName)
) {
return false;
}
if (!doesEffectPropagateToParent(this.node, effectName)) {
return false;
}

if (!this.clause.canHaveEffect(effectName)) {
return false;
}
Expand Down
30 changes: 30 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,3 +244,33 @@ export function validateEffects(spec: Spec, effectsRaw: string[], node: Element)

return effects;
}

export function doesEffectPropagateToParent(node: Element, effect: string) {
// Effects should not propagate past explicit fences in parent steps.
//
// Abstract Closures are considered automatic fences for the user-code
// effect, since those are effectively nested functions.
//
// Calls to Abstract Closures that can call user code must be explicitly
// marked as such with <emu-meta effects="user-code">...</emu-meta>.
for (; node.parentElement; node = node.parentElement) {
const parent = node.parentElement;
// This is super hacky. It's checking the output of ecmarkdown.
if (parent.tagName !== 'LI') continue;

if (effect === 'user-code' && parent.textContent?.includes('be a new Abstract Closure')) {
return false;
}

if (
parent
.getAttribute('fence-effects')
?.split(',')
.map(s => s.trim())
.includes(effect)
) {
return false;
}
}
return true;
}
2 changes: 1 addition & 1 deletion test/baselines/generated-reference/effect-user-code.html
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ <h1><span class="secnum">20</span> ResultOfEvaluating()</h1>
<emu-clause id="sec-fenced-effects" type="abstract operation" aoid="FencedEffects">
<h1><span class="secnum">21</span> FencedEffects()</h1>
<p>The abstract operation FencedEffects takes no arguments. Effects don't propagate past fences in parent steps. A fence must be at the beginning of a step. It performs the following steps when called:</p>
<emu-alg><ol><li fence-effects="user-code">Fence.<ol><li><emu-xref aoid="UserCode" id="_ref_18"><a href="#sec-user-code" class="e-user-code">UserCode</a></emu-xref>().</li></ol></li></ol></emu-alg>
<emu-alg><ol><li fence-effects="user-code">Fence.<ol><li><emu-xref aoid="UserCode" id="_ref_18"><a href="#sec-user-code" class="e-user-code">UserCode</a></emu-xref>().</li><li>Let <var>foo</var> be the result of <span class="e-user-code">evaluating <var>someUserCode</var></span>.</li></ol></li></ol></emu-alg>
</emu-clause>

<emu-clause id="sec-call-fenced-effects" type="abstract operation" aoid="CallFencedEffects">
Expand Down
1 change: 1 addition & 0 deletions test/baselines/sources/effect-user-code.html
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ <h1>FencedEffects()</h1>
<emu-alg>
1. [fence-effects="user-code"] Fence.
1. UserCode().
1. Let _foo_ be the result of evaluating _someUserCode_.
</emu-alg>
</emu-clause>

Expand Down

0 comments on commit da3f316

Please sign in to comment.