diff --git a/src/Algorithm.ts b/src/Algorithm.ts
index cb601212..200c94ab 100644
--- a/src/Algorithm.ts
+++ b/src/Algorithm.ts
@@ -25,7 +25,13 @@ export default class Algorithm extends Builder {
context.inAlg = true;
const { spec, node, clauseStack } = context;
- const innerHTML = node.innerHTML; // TODO use original slice, forward this from linter
+ // Mark all "the result of evaluation Foo" language as having the
+ // "user-code" effect. Do this before ecmarkdown, otherwise productions like
+ // |Foo| get turned into tags and the regexp gets complicated.
+ const innerHTML = node.innerHTML.replace(
+ /the result of evaluating ([a-zA-Z_|0-9]+)/g,
+ 'the result of evaluating $1'
+ ); // TODO use original slice, forward this from linter
let emdTree;
if ('ecmarkdownTree' in node) {
diff --git a/src/Meta.ts b/src/Meta.ts
index b9d59246..b84a084c 100644
--- a/src/Meta.ts
+++ b/src/Meta.ts
@@ -3,10 +3,32 @@ import type { Context } from './Context';
import Builder from './Builder';
+import { validateEffects } from './utils';
+
export default class Meta extends Builder {
static elements = ['EMU-META'];
- static async enter({ spec, node }: Context) {
+ static async enter({ spec, node, clauseStack }: Context) {
+ const parent = clauseStack[clauseStack.length - 1] || null;
+ if (node.hasAttribute('effects') && parent !== null) {
+ const effects = validateEffects(
+ spec,
+ node
+ .getAttribute('effects')!
+ .split(',')
+ .map(c => c.trim()),
+ 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);
+ }
+ }
+ }
spec._emuMetasToRender.add(node);
}
diff --git a/src/Xref.ts b/src/Xref.ts
index bf1b3df4..c96ad5f3 100644
--- a/src/Xref.ts
+++ b/src/Xref.ts
@@ -40,7 +40,11 @@ export default class Xref extends Builder {
// Check if there's metadata adding or suppressing effects
this.addEffects = null;
this.suppressEffects = null;
- if (node.parentElement && node.parentElement.tagName === 'EMU-META') {
+ if (
+ node.parentElement &&
+ node.parentElement.tagName === 'EMU-META' &&
+ node.parentElement.children[0] === node
+ ) {
if (node.parentElement.hasAttribute('effects')) {
const addEffects = node.parentElement.getAttribute('effects')!.split(',');
if (addEffects.length !== 0) {
diff --git a/test/baselines/generated-reference/effect-user-code.html b/test/baselines/generated-reference/effect-user-code.html
index ced6cc64..6cdff506 100644
--- a/test/baselines/generated-reference/effect-user-code.html
+++ b/test/baselines/generated-reference/effect-user-code.html
@@ -4,102 +4,121 @@
1 UserCode ( )
The abstract operation UserCode takes no arguments. It performs the following steps when called:
- - Call user code.
+ - Call user code.
+
+
+
+
+ 2 UserCode2 ( )
+ The abstract operation UserCode2 takes no arguments.
+ This AO calls user code despite not being defined with an algorithm.
- 2 Nop ( )
+ 3 Nop ( )
The abstract operation Nop takes no arguments. It performs the following steps when called:
- Do nothing.
- 3 DirectCall()
+ 4 DirectCall()
The abstract operation DirectCall takes no arguments. Calling AOs that can call user code should insert e-user-code
as a class into the AO link. It performs the following steps when called:
- - UserCode().
+ - UserCode().
- UserCode2().
- 4 TransitiveCall()
+ 5 TransitiveCall()
The abstract operation TransitiveCall takes no arguments. Calling AOs that can transitively call user code should insert e-user-code
as a class into the AO link. It performs the following steps when called:
- - DirectCall().
+ - DirectCall().
- 5 SuppressedDirectCall()
+ 6 SuppressedDirectCall()
The abstract operation SuppressedDirectCall takes no arguments. Can-call-user-code callsites that are suppressed do not get e-user-code
as a class in the AO link. It performs the following steps when called:
- - TransitiveCall().
+ - TransitiveCall().
- 6 SuppressedTransitiveCall()
+ 7 SuppressedTransitiveCall()
The abstract operation SuppressedTransitiveCall takes no arguments. Can-call-user-code callsites that are suppressed do not propagate the effect It performs the following steps when called:
- - SuppressedDirectCall().
+ - SuppressedDirectCall().
- 7 AddedDirectCall()
+ 8 AddedDirectCall()
The abstract operation AddedDirectCall takes no arguments. AOs can have manually added user-code effect at a callsite that propagates. It performs the following steps when called:
- - Nop().
+ - Nop().
- 8 AddedTransitiveCall()
+ 9 AddedTransitiveCall()
The abstract operation AddedTransitiveCall takes no arguments. AOs can have manually added user-code effect at a callsite that propagates. It performs the following steps when called:
- - AddedDirectCall().
+ - AddedDirectCall().
- 9 SDOInvocations()
+ 10 SDOInvocations()
The abstract operation SDOInvocations takes no arguments. SDO-style invocations of AOs that can call user code also have the e-user-code
class in the link. It performs the following steps when called:
- - UserCode of Bar.
+ - UserCode of Bar.
- 10 NonInvocations()
+ 11 NonInvocations()
The abstract operation NonInvocations takes no arguments. Non-invocations (i.e. not followed by () or " of") do not have e-user-code
as a class in the AO link. It performs the following steps when called:
- - UserCode is an abstract operation.
+ - UserCode is an abstract operation.
- 11 NonAbrupt()
+ 12 NonAbrupt()
The abstract operation NonAbrupt takes no arguments. Invocations that cannot result in abrupt completions suppress the user-code effect by default. It performs the following steps when called:
- - Let res be ! UserCode().
+ - Let res be ! UserCode().
- 12 NonAbruptOverride()
+ 13 NonAbruptOverride()
The abstract operation NonAbruptOverride takes no arguments. Invocations that cannot result in abrupt completions suppress the user-code effect by default but can still be overridden. It performs the following steps when called:
- - Let res be ! UserCode().
+ - Let res be ! UserCode().
- 13 Static Semantics: StaticDirectCall
+ 14 Static Semantics: StaticDirectCall
The syntax-directed operation StaticDirectCall takes no arguments. Static semantics suppress user-code.
- - UserCode().
+ - UserCode().
- 14 Static Semantics: StaticTransitiveCall
+ 15 Static Semantics: StaticTransitiveCall
The syntax-directed operation StaticTransitiveCall takes no arguments. Static semantics suppress user-code.
- - StaticDirectCall of Baz.
+ - StaticDirectCall of Baz.
- 15 RenderedMeta()
+ 16 RenderedMeta()
The abstract operation RenderedMeta takes no arguments. emu-meta tags with the effects attribute that aren't surrounding what ecmarkup recognizes as invocations are changed into span tags to be rendered. The effects list is prefixed with e- and changed into class names. It performs the following steps when called:
- Perform ? O.[[Call]]().
- 16 MakeAbstractClosure()
+ 17 MakeAbstractClosure()
The abstract operation MakeAbstractClosure takes no arguments. The user-code effect doesn't propagate through Abstract Closure boundaries by recognizing the "be a new Abstract Closure" substring. It performs the following steps when called:
- - Let closure be a new Abstract Closure that captures nothing and performs the following steps when called:
- UserCode().
- Return closure.
+ - Let closure be a new Abstract Closure that captures nothing and performs the following steps when called:
- UserCode().
- Return closure.
- 17 CallMakeAbstractClosure()
+ 18 CallMakeAbstractClosure()
The abstract operation CallMakeAbstractClosure takes no arguments. The user-code effect doesn't propagate through Abstract Closure boundaries by recognizing the "be a new Abstract Closure" substring. It performs the following steps when called:
- - MakeAbstractClosure().
+ - MakeAbstractClosure().
+
+
+
+ 19 XrefNotFirstChildOfEmuMeta()
+ The abstract operation XrefNotFirstChildOfEmuMeta takes no arguments. Effect additions and suppressions via emu-meta only affect xrefs that are the first child of the emu-meta. Below, UserCode() gets autolinked and get an xref. Its parent element is an emu-meta, but since the xref is not the first child, it should not be interpreted to override the effects of the UserCode() call. It performs the following steps when called:
+ - Perform map.[[DefineOwnProperty]](! UserCode()).
+
+
+
+ 20 ResultOfEvaluating()
+ The abstract operation ResultOfEvaluating takes no arguments. The phrase "the result of evaluating Foo" is automatically considered as can call user code. It performs the following steps when called:
+ - Let res be the result of evaluating Foo.