Skip to content

Instantly share code, notes, and snippets.

@rauschma
Last active August 28, 2024 22:43
Show Gist options
  • Save rauschma/8db338a96f9ead8df0714d233e81fed4 to your computer and use it in GitHub Desktop.
Save rauschma/8db338a96f9ead8df0714d233e81fed4 to your computer and use it in GitHub Desktop.

Stateless RegExp methods

The issues

  • The rules for the flags /g and /y are complicated.
  • Regular expression operations keep state in RegExp objects, via property .lastIndex. That leads to several pitfalls if a RegExp object is used multiple times. It also makes it impossible to freeze instances of RegExp.
  • String.prototype.replace() accepts a callback where accessing named captures is inconvenient (via an object that is passed as the last argument).

The goal

  • Improving the RegExp API without introducing a new constructor.

New methods

Common characteristics:

  • Options object:
    • .startIndex = 0
    • .returnIndices = false
  • Don’t change the RegExp object in any way.
    • Completely ignore .lastIndex.
  • Completely ignore the following flags:
    • /g (.global): not needed because each method is either global or non-global
    • /y (.sticky): replaced by the assertion \G
    • /d (.hasIndices): replaced by option .returnIndices
      • Not as important but I’d prefer an option for a method over a RegExp flag because this toggle is more about how an operation works than about how a RegExp matches.

RegExp.prototype.*

  • .execOnce(str, options?): MatchObject
  • .execMany(str, options?): Iteratable<MatchObject>
  • .testOnce(str, options?): boolean

String.prototype.*

Better callback type signature: callback(matchObject)

  • .replaceOnce(stringOrRegExp, stringOrCallback, options?): string
  • .replaceMany(stringOrRegExp, stringOrCallback, options?): string

Open questions:

  • Should these methods forward to Symbol-keyed properties of stringOrRegExp?
    • Another option: turn them into RegExp.prototype.* methods.

“Legacy” RegExp operations that are already stateless

  • String.prototype.search(patternStringOrRegExp): number
  • String.prototype.split(verbatimStringOrRegExp?, limit?): Array<string>

Open question:

  • Would it make sense to support an additional argument options?

“Legacy” RegExp operations that would become deprecated

  • RegExp.prototype.exec
  • RegExp.prototype.test
  • String.prototype.match
  • String.prototype.matchAll
  • String.prototype.replace
  • String.prototype.replaceAll

Assertion \G

  • Matches at the current matching position (0 or .startIndex).
  • Loosely related to the ^ assertion

How should legacy methods handle \G?

  • It clashes with flag /y because with that flag, a regular expression implicitly starts with \G.
    • Thus: throwing an exception when \G is used with /y seems the best option.
  • Other than that, we could specify a “current position” for all legacy methods (sometimes .lastIndex, sometimes 0) and use that with \G.
  • /y is ignored by .split(), so supporting \G would be an improvement.

Important: Is this proposal compatible with upcoming RegExp features?

Potential upcoming proposal: template tag for regular expressions

  • Currently, there is no plan to support multi-line RegExp literals in JavaScript. A template tag is a good alternative and would be very useful for the proposed flag /x.
  • Two TC39 members have expressed an interest in adding a template tags for RegExps to JavaScript (source).
  • A template tag could look like this: https://github.com/slevithan/regex

FAQ

Why the name suffix Many?

  • All is already taken.
  • It’s just a first idea – suggestions welcome!
    • Other options: Multi, OnceOrMore

Why not a single method with (e.g.) an option .many?

  • I find non-overloaded methods easier to understand (they are also easier to statically type): .execOnce and .execMany have different return types.
  • I also like to avoid single big methods that do too much.
  • Precedents for method pairs in the current API:
    • .replace() and .replaceAll()
    • .match() and .matchAll()

Acknowledgement

@rauschma
Copy link
Author

@slevithan Great feedback, thanks! I updated the Gist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment