From a8f57a607d45cda991c6708cedc0ec4e438ac79b Mon Sep 17 00:00:00 2001 From: Shu-yu Guo Date: Thu, 9 Dec 2021 19:31:11 -0800 Subject: [PATCH] Fixes to user code effect handling (#385) --- src/Algorithm.ts | 8 +- src/Meta.ts | 24 +++++- src/Xref.ts | 6 +- .../generated-reference/effect-user-code.html | 81 ++++++++++++------- test/baselines/sources/effect-user-code.html | 37 ++++++++- 5 files changed, 119 insertions(+), 37 deletions(-) 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:

-
  1. Call user code.
+
  1. 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:

  1. 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:

-
  1. UserCode().
+
  1. UserCode().
  2. 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:

-
  1. DirectCall().
+
  1. 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:

-
  1. TransitiveCall().
+
  1. 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:

-
  1. SuppressedDirectCall().
+
  1. 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:

-
  1. Nop().
+
  1. 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:

-
  1. AddedDirectCall().
+
  1. 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:

-
  1. UserCode of Bar.
+
  1. 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:

-
  1. UserCode is an abstract operation.
+
  1. 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:

-
  1. Let res be ! UserCode().
+
  1. 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:

-
  1. Let res be ! UserCode().
+
  1. 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.

-
  1. UserCode().
+
  1. UserCode().
-

14 Static Semantics: StaticTransitiveCall

+

15 Static Semantics: StaticTransitiveCall

The syntax-directed operation StaticTransitiveCall takes no arguments. Static semantics suppress user-code.

-
  1. StaticDirectCall of Baz.
+
  1. 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:

  1. 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:

-
  1. Let closure be a new Abstract Closure that captures nothing and performs the following steps when called:
    1. UserCode().
  2. Return closure.
+
  1. Let closure be a new Abstract Closure that captures nothing and performs the following steps when called:
    1. UserCode().
  2. 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:

-
  1. MakeAbstractClosure().
+
  1. 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:

+
  1. 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:

+
  1. Let res be the result of evaluating Foo.
\ No newline at end of file diff --git a/test/baselines/sources/effect-user-code.html b/test/baselines/sources/effect-user-code.html index f1612011..785a8cc0 100644 --- a/test/baselines/sources/effect-user-code.html +++ b/test/baselines/sources/effect-user-code.html @@ -7,14 +7,22 @@

UserCode ( )

-
effects
-
user-code
- 1. Call user code. + 1. Call user code.
+ + +

UserCode2 ( )

+
+
effects
+
user-code
+
+

This AO calls user code despite not being defined with an algorithm.

+
+

Nop ( )

@@ -32,6 +40,7 @@

DirectCall()

1. UserCode(). + 1. UserCode2().
@@ -190,3 +199,25 @@

CallMakeAbstractClosure()

1. MakeAbstractClosure(). + + +

XrefNotFirstChildOfEmuMeta()

+
+
description
+
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.
+
+ + 1. Perform _map_.[[DefineOwnProperty]](! UserCode()). + +
+ + +

ResultOfEvaluating()

+
+
description
+
The phrase "the result of evaluating Foo" is automatically considered as can call user code.
+
+ + 1. Let _res_ be the result of evaluating |Foo|. + +