Sourcemeta Core 0.0.0
Loading...
Searching...
No Matches
Regex

An opinionated and permissive ECMA 262 + RFC 9485 (best effort) regex implementation for JSON Schema. More...

Classes

struct  sourcemeta::core::RegexTypeNonEmpty
struct  sourcemeta::core::RegexTypePCRE2
struct  sourcemeta::core::RegexTypeNoop

Typedefs

using sourcemeta::core::RegexTypePrefix = std::string
using sourcemeta::core::RegexTypeRange = std::pair<std::size_t, std::size_t>
using sourcemeta::core::Regex

Enumerations

enum class  sourcemeta::core::RegexDialect : std::uint8_t { RegexDialect::Permissive , RegexDialect::IRegexp , RegexDialect::IRegexpSearch }

Functions

SOURCEMETA_CORE_REGEX_EXPORT auto sourcemeta::core::to_regex (const std::string_view pattern, const RegexDialect dialect=RegexDialect::Permissive, const bool optimise_for_match=true) -> std::optional< Regex >
SOURCEMETA_CORE_REGEX_EXPORT auto sourcemeta::core::matches (const Regex &regex, const std::string_view value) -> bool
SOURCEMETA_CORE_REGEX_EXPORT auto sourcemeta::core::matches_if_valid (const std::string_view pattern, const std::string_view value, const RegexDialect dialect=RegexDialect::Permissive) -> bool
SOURCEMETA_CORE_REGEX_EXPORT auto sourcemeta::core::replace_all (const Regex &regex, const std::string_view subject, const std::string_view replacement) -> std::string
SOURCEMETA_CORE_REGEX_EXPORT auto sourcemeta::core::is_regex_ecma (const std::string_view pattern) -> bool

Detailed Description

An opinionated and permissive ECMA 262 + RFC 9485 (best effort) regex implementation for JSON Schema.

This functionality is included as follows:

#include <sourcemeta/core/regex.h>

Class Documentation

◆ sourcemeta::core::RegexTypeNonEmpty

struct sourcemeta::core::RegexTypeNonEmpty

◆ sourcemeta::core::RegexTypePCRE2

struct sourcemeta::core::RegexTypePCRE2

A regular expression compiled through the PCRE2 engine.

Public Attributes

std::shared_ptr< void > code
 The opaque handle to the compiled expression.

◆ sourcemeta::core::RegexTypeNoop

struct sourcemeta::core::RegexTypeNoop

Typedef Documentation

◆ Regex

Initial value:
std::string RegexTypePrefix
Definition regex.h:31
std::pair< std::size_t, std::size_t > RegexTypeRange
Definition regex.h:40
Definition regex.h:53

A compiled regular expression in one of its supported representations.

◆ RegexTypePrefix

using sourcemeta::core::RegexTypePrefix = std::string

Matches any string that begins with a fixed prefix.

◆ RegexTypeRange

using sourcemeta::core::RegexTypeRange = std::pair<std::size_t, std::size_t>

Matches any string whose length falls within an inclusive range.

Enumeration Type Documentation

◆ RegexDialect

enum class sourcemeta::core::RegexDialect : std::uint8_t
strong

The dialects that a regular expression pattern can be interpreted with.

Enumerator
Permissive 

A permissive superset of ECMA 262 with PCRE2 extensions.

IRegexp 

Strict RFC 9485 I-Regexp, where any pattern outside the grammar is rejected, matching considers the whole input, and an unescaped caret or dollar sign outside a character class is an ordinary character

IRegexpSearch 

Like the strict RFC 9485 dialect, except that matching considers any substring of the input

Function Documentation

◆ is_regex_ecma()

SOURCEMETA_CORE_REGEX_EXPORT auto sourcemeta::core::is_regex_ecma ( const std::string_view pattern) -> bool

Check whether the given string is a valid ECMA-262 regular expression.

The pattern is read against the grammar of ECMA-262, including its early errors, rather than handed to the underlying engine, so the answer follows the standard instead of whatever a particular engine happens to accept. A pattern is accepted when it parses under either of the two Unicode-aware readings of the standard, meaning the one that JavaScript selects with the u flag or the one it selects with the v flag. The latter is what makes set notation, such as nested classes, set operations and string disjunctions, come out valid. JSON Schema recommends the former for the sake of Unicode support.

The reading that JavaScript selects when neither flag is given is deliberately not accepted. That is the reading on which Annex B of the standard applies, and the standard is explicit about who that annex is for: "The ECMAScript language syntax and semantics defined in this annex are required when the ECMAScript host is a web browser. The content of this annex is normative but optional if the ECMAScript host is not a web browser." It is equally explicit about who should stay away from it: "ECMAScript implementations are discouraged from implementing these features unless the implementation is part of a web browser or is required to run the same legacy ECMAScript code that web browsers encounter", and of the features themselves it says that they "are not considered part of the core ECMAScript language". Neither condition holds for reading a schema. Both Unicode-aware readings turn the annex off in any case, as "none of these extensions change the syntax of Unicode patterns recognized when parsing with the [UnicodeMode] parameter present on the goal symbol". See https://tc39.es/ecma262/#sec-additional-ecmascript-features-for-web-browsers and https://tc39.es/ecma262/#sec-regular-expressions-patterns

The practical effect of that annex is to turn a great deal of otherwise malformed input into literal text, so a pattern written for another flavour would pass without meaning what its author intended. Leaving it aside, a syntax character standing on its own, such as an unescaped brace or closing bracket, makes a pattern invalid, an escape of a letter that the standard leaves undefined makes a pattern invalid, and property escapes only resolve against the property names and values that the standard permits. The discarded reading is also the only one that allows escaping a character that cannot continue an identifier, so escaped punctuation outside a character class is turned down as well. A web browser accepts all of these, and this is where the two answers part company. For example:

#include <sourcemeta/core/regex.h>
#include <cassert>
assert(sourcemeta::core::is_regex_ecma("([abc])+\\s+$"));
assert(sourcemeta::core::is_regex_ecma("\\p{gc=Lu}"));
assert(sourcemeta::core::is_regex_ecma("[[a-z]--[aeiou]]"));
assert(!sourcemeta::core::is_regex_ecma("foo\\Kbar"));
SOURCEMETA_CORE_REGEX_EXPORT auto is_regex_ecma(const std::string_view pattern) -> bool

Note that a pattern being valid does not mean this project can compile it, as the standard places no bound on how much a quantifier may repeat nor on how deeply groups and classes may nest, while this reader stops at 255 levels and the underlying engine has limits of its own. Use to_regex to find out whether a pattern can also be matched with, bearing in mind that the dialects it offers are not the reading described here.

◆ matches()

SOURCEMETA_CORE_REGEX_EXPORT auto sourcemeta::core::matches ( const Regex & regex,
const std::string_view value ) -> bool

Validate a string against a regular expression. For example:

#include <sourcemeta/core/regex.h>
#include <cassert>
assert(regex.has_value());
assert(sourcemeta::core::matches(regex.value(), "foo bar"));
SOURCEMETA_CORE_REGEX_EXPORT auto to_regex(const std::string_view pattern, const RegexDialect dialect=RegexDialect::Permissive, const bool optimise_for_match=true) -> std::optional< Regex >
std::variant< RegexTypePrefix, RegexTypeNonEmpty, RegexTypeRange, RegexTypePCRE2, RegexTypeNoop > Regex
Definition regex.h:59
SOURCEMETA_CORE_REGEX_EXPORT auto matches(const Regex &regex, const std::string_view value) -> bool

◆ matches_if_valid()

SOURCEMETA_CORE_REGEX_EXPORT auto sourcemeta::core::matches_if_valid ( const std::string_view pattern,
const std::string_view value,
const RegexDialect dialect = RegexDialect::Permissive ) -> bool

Validate a string against a regular expression pattern if the pattern represents a valid regular expression, compiling it along the way. For example:

#include <sourcemeta/core/regex.h>
#include <cassert>
assert(sourcemeta::core::matches_if_valid("^foo", "foo bar"));
SOURCEMETA_CORE_REGEX_EXPORT auto matches_if_valid(const std::string_view pattern, const std::string_view value, const RegexDialect dialect=RegexDialect::Permissive) -> bool

◆ replace_all()

SOURCEMETA_CORE_REGEX_EXPORT auto sourcemeta::core::replace_all ( const Regex & regex,
const std::string_view subject,
const std::string_view replacement ) -> std::string

Replace every match of a regular expression with the given text, which is inserted literally. The regular expression must have been compiled without optimising for matching, and the behaviour is undefined otherwise. A subject that exhausts a matching resource is left alone, just as it would count as a failure to match. For example:

#include <sourcemeta/core/regex.h>
#include <cassert>
const auto regex{sourcemeta::core::to_regex("[0-9]+",
assert(regex.has_value());
assert(sourcemeta::core::replace_all(regex.value(), "a1b22c", "#") ==
"a#b#c");
SOURCEMETA_CORE_REGEX_EXPORT auto replace_all(const Regex &regex, const std::string_view subject, const std::string_view replacement) -> std::string
@ Permissive
A permissive superset of ECMA 262 with PCRE2 extensions.
Definition regex.h:76

◆ to_regex()

SOURCEMETA_CORE_REGEX_EXPORT auto sourcemeta::core::to_regex ( const std::string_view pattern,
const RegexDialect dialect = RegexDialect::Permissive,
const bool optimise_for_match = true ) -> std::optional< Regex >

Compile a regular expression from a string. If the regular expression is invalid, no value is returned. In this function:

  • Permissive regexes are NOT automatically anchored
  • Permissive regexes assume DOTALL
  • RFC 9485 regexes match the whole input, except in the search dialect, which matches any substring
  • Regexes assume Unicode
  • Regexes are case sensitive
  • No matching happens (only boolean validation)
  • When optimising for matching, a pattern that only needs a yes or no answer may compile to a faster form that cannot rewrite a subject

For example:

#include <sourcemeta/core/regex.h>
#include <cassert>
assert(regex.has_value());