antlr4: ANTLR4 runtime cannot be transpiled to ES5 because of incorrect inheritance

Background

Web applications must be transpiled to ECMAScript 5 (ES5) if they need to support older JavaScript runtimes, for example Internet Explorer or hardware devices that shipped with older JavaScript engines. In the past, the ANTLR runtime was written using ES5 compatible code. But version 4.9.x recently introduced ES6 class definitions that require transpilation. This is fine.

However, the ANTLR library has some classes that inherit from the system Error class, which is incompatible with transpilation. The reason is that ES5 system APIs are modeled as ES6 classes, which use a different inheritance mechanism from transpiled classes. (See this article for some details.)

Repro

  1. Transpile ANTLR4 to ES5 and bundle it into a web application.

This code fails to catch LexerNoViableAltException:

                    catch (e) {
                        if (e instanceof RecognitionException) {
                            this.notifyListeners(e); // report error
                            this.recover(e);
                        }
                        else {
                            console.log(e.stack);
                            throw e;
                        }
                    }

The e instanceof RecognitionException test returns false, even though LexerNoViableAltException inherits from RecognitionException. This happens because of the incorrect prototype introduced when RecognitionException inherits from the Error base class.

Possible fixes

The two standard solutions are:

  1. Don’t inherit from the Error class; instead simply implement its contract using a custom base class. This will break instanceof Error, but generally nobody ever tests that, so this approach works fine in practice. The callstack property can be calculated using (new Error()).stack. - OR -
  2. Call Object.setPrototypeOf() in the constructor for every subclass of the Error class. This also works pretty well. Its main downside is that people sometimes forget to apply the workaround when adding new subclasses.

I would be willing to make a PR to fix this. Would you accept a fix?

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 30 (15 by maintainers)

Commits related to this issue

Most upvoted comments

I’m about to close this since this is not an ANTLR4 runtime issue.

BTW the TypeScript compiler maintainers replied and said they will not implement Babel’s solution because it requires too much extra code. Their behavior is “by design”.

My advice is: don’t use tools written by lazy people (it not incompetent)

We will soon be shipping a typescript target, so closing this.

@octogonz can you move your last comment to the PR itself - best to discuss there ?

@octogonz you could try using this branch https://github.com/ericvergnaud/antlr4/tree/purify-javascript-runtime-code it supposedly generates an ES5 script file

2. @octogonz Can you be more specific about which JS Runtimes the above code fails in (are we talking old versions of Node or IE or some Phone Browsers)?

The code fails if you specify "target": "es5" in your tsconfig.json. The actual JavaScript runtime does not matter – for example my example will fail even with the latest Chrome.

The ECMAScript 5 target is needed mainly for Internet Explorer 11, and for devices that shipped with an older web browser (e.g. Playstation, Tizen, etc). These devices can last for many years beyond their initial release, and the OEM generally will never update the browser beyond security fixes. Recently at work I encountered a Comcast TV box that mostly supports ECMAScript 6, but with arbitrary gaps that are most easily solved by transpiling to ES5.

ES6 is now 6 years old. I’ll be overjoyed on the day when we can finally forget about ES5, but we’re not there yet…

I am not super inclined to add redundant code when the issue seems to lie with the TypeScript transpiler.

I’m not recommending to add redundant code. My recommendation is to avoid extending the system Error class. This is the best practice for a library that intends to be used in many different environments.

I can also open a TypeScript issue proposing for the transpiler to call Object.setPrototypeOf(), but (from previous experience) they might have some complicated justification for why they don’t do that.

Also, if you are using TypeScript, should you not be using the TypeScript runtime target ? I would expect the authors would have addressed that problem.

ANTLR’s TypeScript target is still experimental and not officially supported. It might be nice to have, but frankly since humans don’t edit the generated parsers, it doesn’t matter much whether the generated code is typed or not. @types/antlr4 provides typings for the ANTLR runtime, and for my case at least, I only had to write about 10 lines of code to add typings for the generated classes. So I’m fine using the JavaScript target. Except for this one problem. 😃