• Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
    Dedicated community for Japanese speakers
  • 한국 커뮤니티
    Dedicated community for Korean speakers
Exit
0

[BUG] ExtendScript incorrectly handles the "continue" statement within "switch" statements

Enthusiast ,
Jan 28, 2018 Jan 28, 2018

Copy link to clipboard

Copied

Description

ExtendScript does not handle the continue statement according to the ECMAScript 3rd Edition standard when located within a case clause of a switch statement. The problem is slightly different when a continue statement is placed within a switch statement's default clause. The specific issues are as follows:

  1. Within a [switch-case] statement: The continue statement is ignored.
  2. Within a [switch-default] statement: The continue statement breaks out of the default clause of the switch statement and continues processing after the switch statement, rather than with the next iteration of the enclosing loop.

This can cause fairly straightforward logic to explode in surprising ways. It can also make it difficult to use modern technologies with ExtendScript (e.g. transpilers).

Details

This is a simple test-case:

var test = function()

{

    for (var i = 0; i < 3; ++i)

    {

        $.writeln("Pre Switch");

       

        switch(i)

        {

            case 0:

                $.writeln("\tCase 0");

                break;

            case 1:

                $.writeln("\tCase 1");

                continue;

            default:

                $.writeln("\tStart Default");

                if (i == 2)

                {

                    $.writeln("\t\tAt 2");

                    continue;

                }

                $.writeln("\tEnd Default");

        }

        $.writeln("Post Switch");

    }

}

test();

Current Erroneous Behavior

Running this in the ExtendScript Toolkit (v4.0.0.1, ExtendScript 4.5.5) produces the following output:

Pre Switch

Case 0

Post Switch

Pre Switch

Case 1

Start Default

End Default

Post Switch

Pre Switch

Start Default

At 2

Post Switch

Lines that should not be printed are highlighted in red. There are two groupings of incorrect functionality, one for each of the two continue statements. Explanations of what is happening in these two cases follows:

  1. The first three erroneous print statements occurred because the continue statement at line 14 was ignored. It should have caused processing to return to the top of the for-loop at line 3. Instead, processing falls-through into the default clause.
  2. The last erroneous print statement occurred because the continue statement at line 20 did not correctly cause processing to return to the top of the for-loop at line 3. Instead, it caused processing to skip the rest of default clause and then continue after the end of the switch statement.

Expected Behavior

This is the expected output:

Pre Switch

Case 0

Post Switch

Pre Switch

Case 1

Pre Switch

Start Default

At 2

That output was generated in the JavaScript console of the Chrome 64 browser. It is possible to run the test code provided above​ in a browser context as long as you first run the following:

var $ = {

    writeln: console.log

};

This creates a $ object with a writeln function that simply points to the standard console.log function. Once this has been processed in the browser's JavaScript context, the test script provided will run without issue.

Notes

According to the ECMAScript 3rd Edition spec [p. 67], continue statements always refer to "Iteration Statements" (for, while, do-while loops). They should ignore switch statements entirely.

Views

1.4K

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
People's Champ ,
Jan 29, 2018 Jan 29, 2018

Copy link to clipboard

Copied

Just don't use Switch. I don't say that as a lazy answer but as a generic good practice in ExtendScript (performance issues If I recall well). It's likely that you will always have a way not to use it.

But sure, bugs can be.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Jan 29, 2018 Jan 29, 2018

Copy link to clipboard

Copied

Loic.Aigon  wrote

Just don't use Switch. I don't say that as a lazy answer but as a generic good practice in ExtendScript (performance issues If I recall well). It's likely that you will always have a way not to use it.

Of course. You can always use an extended if-else tree instead (although fall-through cases are a bit less clear).

That said, people are resorting more and more to using third-party JavaScript code. One major example is JSON​, which Adobe themselves use in their internal panels. I've checked JSON2 and JSON3 and neither of those contain the continue statement. But this does not hold true for all code out there.

One more complex example is that of TypeScript, which allows you to use modern programming practices through syntactic downleveling​ (e.g. async functions and generators/iteration). TypeScript can emit ECMAScript 3 valid code even though it uses ECMAScript 5+ features. Unfortunately, transpiled generator code makes use of the continue statement which completely breaks the feature in today's ExtendScript.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Aug 23, 2021 Aug 23, 2021

Copy link to clipboard

Copied

LATEST

The "transpiler" link in my original post predates the ability to share links with TSConfig settings properly communicated. The current TypeScript Playground now includes that setting. This newer link shows the problematic output.

 

In short, it converts this:

 

function* idMaker() {
 let index = 0;
 while(index < 3)
 yield index++;
}

let gen = idMaker();

$.writeln(gen.next()); // { value: 0, done: false }
$.writeln(gen.next()); // { value: 1, done: false }
$.writeln(gen.next()); // { value: 2, done: false }
$.writeln(gen.next()); // { done: true }

 

into this:

 

"use strict";
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
function idMaker() {
    var index;
    return __generator(this, function (_a) {
        switch (_a.label) {
            case 0:
                index = 0;
                _a.label = 1;
            case 1:
                if (!(index < 3)) return [3 /*break*/, 3];
                return [4 /*yield*/, index++];
            case 2:
                _a.sent();
                return [3 /*break*/, 1];
            case 3: return [2 /*return*/];
        }
    });
}
var gen = idMaker();
$.writeln(gen.next()); // { value: 0, done: false }
$.writeln(gen.next()); // { value: 1, done: false }
$.writeln(gen.next()); // { value: 2, done: false }
$.writeln(gen.next()); // { done: true }

 

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines