Current section

Files

Jump to
re2 c_src re2_nif.cc
Raw

c_src/re2_nif.cc

// Copyright (c) 2010-2018 Tuncer Ayaz. All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include <erl_nif.h>
#include <re2/re2.h>
#include <map>
#include <vector>
#include <memory>
#ifdef DEBUG
#include <iostream>
#define DBG(M) \
do { \
std::cerr << "re2: " << M; \
} while (false)
#else
#define DBG(M) \
do { \
} while (false)
#endif
namespace {
struct compileoptions
{
re2::RE2::Options re2opts;
};
struct matchoptions
{
enum value_spec
{
VS_ALL,
VS_ALL_BUT_FIRST,
VS_FIRST,
VS_NONE,
VS_VLIST
};
enum capture_type
{
CT_INDEX,
CT_LIST,
CT_BINARY
};
bool caseless;
int offset;
value_spec vs;
capture_type ct;
ERL_NIF_TERM vlist;
matchoptions(ErlNifEnv* env)
: caseless(false)
, offset(0)
, vs(VS_ALL)
, ct(CT_BINARY)
{
vlist = enif_make_list(env, 0);
}
};
struct replaceoptions
{
bool global;
replaceoptions()
: global(false)
{}
};
// Cleanup function for C++ object created with enif allocator via C++
// placement syntax which necessitates explicit invocation of the object's
// destructor. This is used in the NIF resource cleanup callback and in a
// unique_ptr's deleter.
template <typename T>
void cleanup_obj_ptr(T*& ptr)
{
if (ptr != nullptr) {
ptr->~T();
enif_free(ptr);
ptr = nullptr;
}
}
// Deleter for use with enif allocated C++ object owned by a unique_ptr.
template <typename T>
struct EnifDeleter
{
void operator()(T* ptr) { cleanup_obj_ptr(ptr); }
};
using Re2UniquePtr = std::unique_ptr<re2::RE2, EnifDeleter<re2::RE2>>;
} // namespace
struct re2_handle
{
// RE2 objects are thread safe. no locking required.
re2::RE2* re;
};
//
// Use a union for pointer type conversion to avoid compiler warnings
// about strict-aliasing violations with gcc-4.1. gcc >= 4.2 does not
// emit the warning.
// TODO: Reconsider use of union once gcc-4.1 is obsolete?
//
union re2_handle_union
{
void* vp;
re2_handle* p;
};
#if ERL_NIF_MAJOR_VERSION > 2 \
|| (ERL_NIF_MAJOR_VERSION == 2 && ERL_NIF_MINOR_VERSION >= 7)
#define NIF_FUNC_ENTRY(name, arity, fun) \
{ \
name, arity, fun, 0 \
}
#else
#define NIF_FUNC_ENTRY(name, arity, fun) \
{ \
name, arity, fun \
}
#endif
#if ERL_NIF_MAJOR_VERSION > 2
#define RE2_HAVE_DIRTY_SCHEDULERS 1
#elif ERL_NIF_MAJOR_VERSION == 2
#if ERL_NIF_MINOR_VERSION >= 7 && ERL_NIF_MINOR_VERSION < 11
#ifdef ERL_NIF_DIRTY_SCHEDULER_SUPPORT
#define RE2_HAVE_DIRTY_SCHEDULERS 1
#endif
#elif ERL_NIF_MINOR_VERSION >= 11
#define RE2_HAVE_DIRTY_SCHEDULERS 1
#else
#undef RE2_HAVE_DIRTY_SCHEDULERS
#endif
#endif
#ifdef RE2_HAVE_DIRTY_SCHEDULERS
static bool have_online_dirty_schedulers()
{
ErlNifSysInfo si;
enif_system_info(&si, sizeof(si));
DBG("dirty_scheduler_support: " << si.dirty_scheduler_support << "\n");
return si.dirty_scheduler_support != 0;
}
#define DS_MODE ERL_NIF_DIRTY_JOB_CPU_BOUND
#define SCHEDULE_NIF enif_schedule_nif
#else
static bool have_online_dirty_schedulers()
{
return false;
}
#define DS_MODE 0
static ERL_NIF_TERM SCHEDULE_NIF(
ErlNifEnv* env,
const char*, // fun_name
int, // flags
ERL_NIF_TERM (*fp)(ErlNifEnv*, int, const ERL_NIF_TERM[]),
int argc,
const ERL_NIF_TERM argv[])
{
return (*fp)(env, argc, argv);
}
#endif
// static variables
static int ds_flags = 0;
static ErlNifResourceType* re2_resource_type = nullptr;
static ERL_NIF_TERM a_ok;
static ERL_NIF_TERM a_error;
static ERL_NIF_TERM a_match;
static ERL_NIF_TERM a_nomatch;
static ERL_NIF_TERM a_capture;
static ERL_NIF_TERM a_global;
static ERL_NIF_TERM a_offset;
static ERL_NIF_TERM a_all;
static ERL_NIF_TERM a_all_but_first;
static ERL_NIF_TERM a_first;
static ERL_NIF_TERM a_none;
static ERL_NIF_TERM a_index;
static ERL_NIF_TERM a_binary;
static ERL_NIF_TERM a_caseless;
static ERL_NIF_TERM a_max_mem;
static ERL_NIF_TERM a_err_enif_alloc_binary;
static ERL_NIF_TERM a_err_enif_alloc_resource;
static ERL_NIF_TERM a_err_enif_alloc;
static ERL_NIF_TERM a_err_enif_get_atom;
static ERL_NIF_TERM a_err_enif_get_string;
static ERL_NIF_TERM a_re2_NoError;
static ERL_NIF_TERM a_re2_ErrorInternal;
static ERL_NIF_TERM a_re2_ErrorBadEscape;
static ERL_NIF_TERM a_re2_ErrorBadCharClass;
static ERL_NIF_TERM a_re2_ErrorBadCharRange;
static ERL_NIF_TERM a_re2_ErrorMissingBracket;
static ERL_NIF_TERM a_re2_ErrorMissingParen;
static ERL_NIF_TERM a_re2_ErrorTrailingBackslash;
static ERL_NIF_TERM a_re2_ErrorRepeatArgument;
static ERL_NIF_TERM a_re2_ErrorRepeatSize;
static ERL_NIF_TERM a_re2_ErrorRepeatOp;
static ERL_NIF_TERM a_re2_ErrorBadPerlOp;
static ERL_NIF_TERM a_re2_ErrorBadUTF8;
static ERL_NIF_TERM a_re2_ErrorBadNamedCapture;
static ERL_NIF_TERM a_re2_ErrorPatternTooLarge;
static void init_atoms(ErlNifEnv* env)
{
a_ok = enif_make_atom(env, "ok");
a_error = enif_make_atom(env, "error");
a_match = enif_make_atom(env, "match");
a_nomatch = enif_make_atom(env, "nomatch");
a_capture = enif_make_atom(env, "capture");
a_global = enif_make_atom(env, "global");
a_offset = enif_make_atom(env, "offset");
a_all = enif_make_atom(env, "all");
a_all_but_first = enif_make_atom(env, "all_but_first");
a_first = enif_make_atom(env, "first");
a_none = enif_make_atom(env, "none");
a_index = enif_make_atom(env, "index");
a_binary = enif_make_atom(env, "binary");
a_caseless = enif_make_atom(env, "caseless");
a_max_mem = enif_make_atom(env, "max_mem");
a_err_enif_alloc_binary = enif_make_atom(env, "enif_alloc_binary");
a_err_enif_alloc_resource = enif_make_atom(env, "enif_alloc_resource");
a_err_enif_alloc = enif_make_atom(env, "enif_alloc");
a_err_enif_get_atom = enif_make_atom(env, "enif_get_atom");
a_err_enif_get_string = enif_make_atom(env, "enif_get_string");
a_re2_NoError = enif_make_atom(env, "no_error");
a_re2_ErrorInternal = enif_make_atom(env, "internal");
a_re2_ErrorBadEscape = enif_make_atom(env, "bad_escape");
a_re2_ErrorBadCharClass = enif_make_atom(env, "bad_char_class");
a_re2_ErrorBadCharRange = enif_make_atom(env, "bad_char_range");
a_re2_ErrorMissingBracket = enif_make_atom(env, "missing_bracket");
a_re2_ErrorMissingParen = enif_make_atom(env, "missing_paren");
a_re2_ErrorTrailingBackslash = enif_make_atom(env, "trailing_backslash");
a_re2_ErrorRepeatArgument = enif_make_atom(env, "repeat_argument");
a_re2_ErrorRepeatSize = enif_make_atom(env, "repeat_size");
a_re2_ErrorRepeatOp = enif_make_atom(env, "repeat_op");
a_re2_ErrorBadPerlOp = enif_make_atom(env, "bad_perl_op");
a_re2_ErrorBadUTF8 = enif_make_atom(env, "bad_utf8");
a_re2_ErrorBadNamedCapture = enif_make_atom(env, "bad_named_capture");
a_re2_ErrorPatternTooLarge = enif_make_atom(env, "pattern_too_large");
}
static void cleanup_handle(re2_handle* handle)
{
cleanup_obj_ptr(handle->re);
}
//
// Make an error tuple
//
static ERL_NIF_TERM error(ErlNifEnv* env, const ERL_NIF_TERM err)
{
return enif_make_tuple2(env, a_error, err);
}
//
// convert RE2 error code to error term
//
static ERL_NIF_TERM re2error(ErlNifEnv* env, const re2::RE2& re)
{
ERL_NIF_TERM code;
switch (re.error_code()) {
case re2::RE2::ErrorInternal: // Unexpected error
code = a_re2_ErrorInternal;
break;
// Parse errors
case re2::RE2::ErrorBadEscape: // bad escape sequence
code = a_re2_ErrorBadEscape;
break;
case re2::RE2::ErrorBadCharClass: // bad character class
code = a_re2_ErrorBadCharClass;
break;
case re2::RE2::ErrorBadCharRange: // bad character class range
code = a_re2_ErrorBadCharRange;
break;
case re2::RE2::ErrorMissingBracket: // missing closing ]
code = a_re2_ErrorMissingBracket;
break;
case re2::RE2::ErrorMissingParen: // missing closing )
code = a_re2_ErrorMissingParen;
break;
case re2::RE2::ErrorTrailingBackslash: // trailing \ at end of regexp
code = a_re2_ErrorTrailingBackslash;
break;
case re2::RE2::ErrorRepeatArgument: // repeat argument missing, e.g. "*"
code = a_re2_ErrorRepeatArgument;
break;
case re2::RE2::ErrorRepeatSize: // bad repetition argument
code = a_re2_ErrorRepeatSize;
break;
case re2::RE2::ErrorRepeatOp: // bad repetition operator
code = a_re2_ErrorRepeatOp;
break;
case re2::RE2::ErrorBadPerlOp: // bad perl operator
code = a_re2_ErrorBadPerlOp;
break;
case re2::RE2::ErrorBadUTF8: // invalid UTF-8 in regexp
code = a_re2_ErrorBadUTF8;
break;
case re2::RE2::ErrorBadNamedCapture: // bad named capture group
code = a_re2_ErrorBadNamedCapture;
break;
case re2::RE2::ErrorPatternTooLarge: // pattern too large (compile failed)
code = a_re2_ErrorPatternTooLarge;
break;
default:
case re2::RE2::NoError:
code = a_re2_NoError;
break;
}
ERL_NIF_TERM error
= enif_make_string(env, re.error().c_str(), ERL_NIF_LATIN1);
ERL_NIF_TERM error_arg
= enif_make_string(env, re.error_arg().c_str(), ERL_NIF_LATIN1);
return enif_make_tuple2(
env, a_error, enif_make_tuple3(env, code, error, error_arg));
}
static char* alloc_atom(ErlNifEnv* env, const ERL_NIF_TERM atom, unsigned* len)
{
unsigned atom_len;
if (!enif_get_atom_length(env, atom, &atom_len, ERL_NIF_LATIN1))
return nullptr;
atom_len++;
*len = atom_len;
return (char*)enif_alloc(atom_len);
}
static char* alloc_str(ErlNifEnv* env, const ERL_NIF_TERM list, unsigned* len)
{
unsigned list_len;
if (!enif_get_list_length(env, list, &list_len))
return nullptr;
list_len++;
*len = list_len;
return (char*)enif_alloc(list_len);
}
// ===========
// re2:compile
// ===========
//
// Options = [ Option ]
// Option = caseless | {max_mem, int()}
//
static bool parse_compile_options(
ErlNifEnv* env, const ERL_NIF_TERM list, re2::RE2::Options& opts)
{
if (enif_is_empty_list(env, list))
return true;
ERL_NIF_TERM L, H, T;
for (L = list; enif_get_list_cell(env, L, &H, &T); L = T) {
const ERL_NIF_TERM* tuple;
int tuplearity = -1;
if (enif_is_identical(H, a_caseless)) {
// caseless
opts.set_case_sensitive(false);
} else if (enif_get_tuple(env, H, &tuplearity, &tuple)) {
if (tuplearity == 2) {
if (enif_is_identical(tuple[0], a_max_mem)) {
// {max_mem, int()}
int max_mem = 0;
if (enif_get_int(env, tuple[1], &max_mem))
opts.set_max_mem(max_mem);
else
return false;
}
}
} else {
return false;
}
}
return true;
}
static ERL_NIF_TERM re2_compile_impl(
ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ErlNifBinary pdata;
if (enif_inspect_iolist_as_binary(env, argv[0], &pdata)) {
const re2::StringPiece p((const char*)pdata.data, pdata.size);
re2_handle* handle = (re2_handle*)enif_alloc_resource(
re2_resource_type, sizeof(re2_handle));
if (handle == nullptr) {
return error(env, a_err_enif_alloc_resource);
}
handle->re = nullptr;
re2::RE2::Options re2opts;
re2opts.set_log_errors(false);
if (argc == 2 && !parse_compile_options(env, argv[1], re2opts)) {
cleanup_handle(handle);
enif_release_resource(handle);
return enif_make_badarg(env);
}
re2::RE2* re2 = (re2::RE2*)enif_alloc(sizeof(re2::RE2));
if (re2 == nullptr) {
cleanup_handle(handle);
enif_release_resource(handle);
return error(env, a_err_enif_alloc);
}
handle->re = new (re2) re2::RE2(p, re2opts); // placement new
if (!handle->re->ok()) {
ERL_NIF_TERM error = re2error(env, *(handle->re));
cleanup_handle(handle);
enif_release_resource(handle);
return error;
}
ERL_NIF_TERM result = enif_make_resource(env, handle);
enif_release_resource(handle);
return enif_make_tuple2(env, a_ok, result);
} else {
return enif_make_badarg(env);
}
}
// =========
// re2:match
// =========
static void parse_match_capture_options(
ErlNifEnv* env,
matchoptions& opts,
const ERL_NIF_TERM* tuple,
int tuplearity)
{
bool vs_set = false;
if (enif_is_atom(env, tuple[1])) {
// ValueSpec = all | all_but_first | first | none
if (enif_is_atom(env, tuple[1]) > 0) {
if (enif_is_identical(tuple[1], a_all))
opts.vs = matchoptions::VS_ALL;
else if (enif_is_identical(tuple[1], a_all_but_first))
opts.vs = matchoptions::VS_ALL_BUT_FIRST;
else if (enif_is_identical(tuple[1], a_first))
opts.vs = matchoptions::VS_FIRST;
else if (enif_is_identical(tuple[1], a_none))
opts.vs = matchoptions::VS_NONE;
vs_set = true;
}
} else if (!enif_is_empty_list(env, tuple[1])) {
// ValueSpec = ValueList
// ValueList = [ ValueID ]
// ValueID = int() | string() | atom()
opts.vlist = tuple[1];
vs_set = true;
opts.vs = matchoptions::VS_VLIST;
}
// Type = index | binary
if (tuplearity == 3 && vs_set) {
if (enif_is_identical(tuple[2], a_index))
opts.ct = matchoptions::CT_INDEX;
else if (enif_is_identical(tuple[2], a_binary))
opts.ct = matchoptions::CT_BINARY;
}
}
//
// Options = [ Option ]
// Option = caseless | {offset, non_neg_integer()}
// | {capture,ValueSpec} | {capture,ValueSpec,Type}
// Type = index | binary
// ValueSpec = all | all_but_first | first | none | ValueList
// ValueList = [ ValueID ]
// ValueID = int() | string() | atom()
//
static bool parse_match_options(
ErlNifEnv* env, const ERL_NIF_TERM list, matchoptions& opts)
{
if (enif_is_empty_list(env, list))
return true;
ERL_NIF_TERM L, H, T;
for (L = list; enif_get_list_cell(env, L, &H, &T); L = T) {
const ERL_NIF_TERM* tuple;
int tuplearity = -1;
if (enif_is_identical(H, a_caseless)) {
// caseless
opts.caseless = true;
} else if (enif_get_tuple(env, H, &tuplearity, &tuple)) {
if (tuplearity == 2 || tuplearity == 3) {
// {offset,N} or {capture,ValueSpec}
if (enif_is_identical(tuple[0], a_offset)) {
// {offset, int()}
int offset = 0;
if (enif_get_int(env, tuple[1], &offset)) {
opts.offset = offset;
} else {
return false;
}
} else if (enif_is_identical(tuple[0], a_capture)) {
// {capture,ValueSpec,Type}
parse_match_capture_options(env, opts, tuple, tuplearity);
}
}
} else {
return false;
}
}
return true;
}
//
// build result for re2:match
//
static ERL_NIF_TERM mres(
ErlNifEnv* env,
const re2::StringPiece& str,
const re2::StringPiece& match,
const matchoptions::capture_type ct)
{
switch (ct) {
case matchoptions::CT_BINARY:
ErlNifBinary bmatch;
if (!enif_alloc_binary(match.size(), &bmatch))
return a_err_enif_alloc_binary;
memcpy(bmatch.data, match.data(), match.size());
return enif_make_binary(env, &bmatch);
default:
case matchoptions::CT_INDEX:
int l, r;
if (match.empty()) {
l = -1;
r = 0;
} else {
l = match.data() - str.data();
r = match.size();
}
return enif_make_tuple2(
env, enif_make_int(env, l), enif_make_int(env, r));
}
}
static ERL_NIF_TERM re2_match_ret_vlist(
ErlNifEnv* env,
const re2::RE2& re,
const re2::StringPiece& s,
const matchoptions& opts,
std::vector<re2::StringPiece>& group,
int n)
{
std::vector<ERL_NIF_TERM> vec;
const auto& nmap = re.NamedCapturingGroups();
ERL_NIF_TERM VL, VH, VT;
// empty StringPiece for unfound ValueIds
const re2::StringPiece empty;
for (VL = opts.vlist; enif_get_list_cell(env, VL, &VH, &VT); VL = VT) {
int nid = 0;
if (enif_get_int(env, VH, &nid) && nid > 0) {
// ValueID int()
if (nid < n) {
const re2::StringPiece match = group[nid];
ERL_NIF_TERM res;
if (!match.empty())
res = mres(env, s, group[nid], opts.ct);
else
res = mres(env, s, empty, opts.ct);
if (enif_is_identical(res, a_err_enif_alloc_binary))
return error(env, a_err_enif_alloc_binary);
else
vec.push_back(res);
} else {
vec.push_back(mres(env, s, empty, opts.ct));
}
} else if (enif_is_atom(env, VH)) {
// ValueID atom()
unsigned atom_len;
char* a_id = alloc_atom(env, VH, &atom_len);
if (a_id == nullptr)
return error(env, a_err_enif_alloc);
if (enif_get_atom(env, VH, a_id, atom_len, ERL_NIF_LATIN1) > 0) {
auto it = nmap.find(a_id);
ERL_NIF_TERM res;
if (it != nmap.end())
res = mres(env, s, group[it->second], opts.ct);
else
res = mres(env, s, empty, opts.ct);
if (enif_is_identical(res, a_err_enif_alloc_binary)) {
enif_free(a_id);
return error(env, a_err_enif_alloc_binary);
} else {
vec.push_back(res);
}
} else {
enif_free(a_id);
return error(env, a_err_enif_get_atom);
}
enif_free(a_id);
} else {
// ValueID string()
unsigned str_len;
char* str_id = alloc_str(env, VH, &str_len);
if (str_id == nullptr)
return error(env, a_err_enif_alloc);
if (enif_get_string(env, VH, str_id, str_len, ERL_NIF_LATIN1)
> 0) {
auto it = nmap.find(str_id);
ERL_NIF_TERM res;
if (it != nmap.end())
res = mres(env, s, group[it->second], opts.ct);
else
res = mres(env, s, empty, opts.ct);
if (enif_is_identical(res, a_err_enif_alloc_binary)) {
enif_free(str_id);
return error(env, a_err_enif_alloc_binary);
} else {
vec.push_back(res);
}
} else {
enif_free(str_id);
return error(env, a_err_enif_get_string);
}
enif_free(str_id);
}
}
ERL_NIF_TERM list = enif_make_list_from_array(env, &vec[0], vec.size());
return enif_make_tuple2(env, a_match, list);
}
//
// Get number of capturing groups we want to request from RE2.
//
// It's more efficient to avoid requesting all capturing groups if we need none
// or just the first one.
//
static int number_of_capturing_groups(
int nr_groups, matchoptions::value_spec vs)
{
switch (vs) {
case matchoptions::VS_NONE:
return 0;
case matchoptions::VS_FIRST:
return 1;
default:
return nr_groups;
}
}
static ERL_NIF_TERM re2_match_impl(
ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ErlNifBinary sdata;
if (enif_inspect_iolist_as_binary(env, argv[0], &sdata)) {
const re2::StringPiece s((const char*)sdata.data, sdata.size);
re2::RE2* re = nullptr;
Re2UniquePtr re_unique_ptr = nullptr;
union re2_handle_union handle;
ErlNifBinary pdata;
matchoptions opts(env);
if (argc == 3 && !parse_match_options(env, argv[2], opts))
return enif_make_badarg(env);
if (enif_get_resource(env, argv[1], re2_resource_type, &handle.vp)
&& handle.p->re != nullptr) {
// Save existing RE2 obj for use in this function
re = handle.p->re;
if (opts.caseless) // caseless allowed either in compile or match
return enif_make_badarg(env);
} else if (enif_inspect_iolist_as_binary(env, argv[1], &pdata)) {
const re2::StringPiece p((const char*)pdata.data, pdata.size);
re2::RE2::Options re2opts;
re2opts.set_log_errors(false);
if (opts.caseless)
re2opts.set_case_sensitive(false);
re2::RE2* re2 = (re2::RE2*)enif_alloc(sizeof(re2::RE2));
if (re2 == nullptr)
return error(env, a_err_enif_alloc);
// Save temporary RE2 obj for use in this function
re = new (re2) re2::RE2(p, re2opts); // placement new
// Save RE2 obj ptr for cleanup via unique_ptr
re_unique_ptr.reset(re);
} else {
return enif_make_badarg(env);
}
if (!re->ok())
return enif_make_badarg(env);
// nr_groups must be the number of capturing groups + 1 because
// group[0] will be the text matched by the entire pattern, group[1]
// will be the first capturing group et cetera (assuming n >= 2), if
// there are any capturing groups in the regex and opts.vs causes us to
// request them.
const int nr_groups = re->NumberOfCapturingGroups() + 1;
const int n = number_of_capturing_groups(nr_groups, opts.vs);
std::vector<re2::StringPiece> group;
group.reserve(n);
if (re->Match(
s,
opts.offset,
s.size(),
re2::RE2::UNANCHORED,
group.data(),
n)) {
int start = 0;
int arrsz = n;
if (opts.vs == matchoptions::VS_NONE) {
// return match atom only
return a_match;
} else if (opts.vs == matchoptions::VS_FIRST) {
// return first match only
ERL_NIF_TERM first = mres(env, s, group[0], opts.ct);
if (enif_is_identical(first, a_err_enif_alloc_binary)) {
return error(env, a_err_enif_alloc_binary);
} else {
return enif_make_tuple2(
env, a_match, enif_make_list1(env, first));
}
} else if (opts.vs == matchoptions::VS_ALL_BUT_FIRST) {
// skip first match
start = 1;
arrsz--;
}
if (opts.vs == matchoptions::VS_VLIST) {
// return matched subpatterns as specified in ValueList
return re2_match_ret_vlist(env, *re, s, opts, group, n);
} else {
// return all or all_but_first matches
ERL_NIF_TERM* arr
= (ERL_NIF_TERM*)enif_alloc(sizeof(ERL_NIF_TERM) * n);
for (int i = start, arridx = 0; i < n; i++, arridx++) {
ERL_NIF_TERM res = mres(env, s, group[i], opts.ct);
if (enif_is_identical(res, a_err_enif_alloc_binary)) {
enif_free(arr);
return error(env, a_err_enif_alloc_binary);
} else {
arr[arridx] = res;
}
}
ERL_NIF_TERM list = enif_make_list_from_array(env, arr, arrsz);
enif_free(arr);
return enif_make_tuple2(env, a_match, list);
}
} else {
return a_nomatch;
}
} else {
return enif_make_badarg(env);
}
}
// ===========
// re2:replace
// ===========
//
// Options = [ Option ]
// Option = global
//
static bool parse_replace_options(
ErlNifEnv* env, const ERL_NIF_TERM list, replaceoptions& opts)
{
if (enif_is_empty_list(env, list))
return true;
ERL_NIF_TERM L, H, T;
for (L = list; enif_get_list_cell(env, L, &H, &T); L = T) {
if (enif_is_identical(H, a_global))
opts.global = true;
else
return false;
}
return true;
}
//
// build result for re2:replace
//
static ERL_NIF_TERM rres(ErlNifEnv* env, const std::string& s)
{
ErlNifBinary bsubst;
if (!enif_alloc_binary(s.size(), &bsubst))
return error(env, a_err_enif_alloc_binary);
memcpy(bsubst.data, s.data(), s.size());
return enif_make_binary(env, &bsubst);
}
static ERL_NIF_TERM re2_replace_impl(
ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ErlNifBinary sdata, rdata;
if (enif_inspect_iolist_as_binary(env, argv[0], &sdata)
&& enif_inspect_iolist_as_binary(env, argv[2], &rdata)) {
std::string s((const char*)sdata.data, sdata.size);
const re2::StringPiece r((const char*)rdata.data, rdata.size);
re2::RE2* re = nullptr;
Re2UniquePtr re_unique_ptr = nullptr;
union re2_handle_union handle;
ErlNifBinary pdata;
if (enif_get_resource(env, argv[1], re2_resource_type, &handle.vp)
&& handle.p->re != nullptr) {
// Save existing RE2 obj for use in this function
re = handle.p->re;
} else if (enif_inspect_iolist_as_binary(env, argv[1], &pdata)) {
const re2::StringPiece p((const char*)pdata.data, pdata.size);
re2::RE2::Options re2opts;
re2opts.set_log_errors(false);
re2::RE2* re2 = (re2::RE2*)enif_alloc(sizeof(re2::RE2));
if (re2 == nullptr)
return error(env, a_err_enif_alloc);
// Save temporary RE2 obj for use in this function
re = new (re2) re2::RE2(p, re2opts); // placement new
// Save RE2 obj ptr for cleanup via unique_ptr
re_unique_ptr.reset(re);
} else {
return enif_make_badarg(env);
}
if (!re->ok())
return enif_make_badarg(env);
replaceoptions opts;
if (argc == 4 && !parse_replace_options(env, argv[3], opts))
return enif_make_badarg(env);
if (opts.global) {
if (re2::RE2::GlobalReplace(&s, *re, r)) {
return rres(env, s);
} else {
return a_error;
}
} else {
if (re2::RE2::Replace(&s, *re, r)) {
return rres(env, s);
} else {
return a_error;
}
}
} else {
return enif_make_badarg(env);
}
}
extern "C" {
static ERL_NIF_TERM re2_compile(
ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
return SCHEDULE_NIF(
env, "compile", ds_flags, &re2_compile_impl, argc, argv);
}
static ERL_NIF_TERM re2_match(
ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
return SCHEDULE_NIF(env, "match", ds_flags, &re2_match_impl, argc, argv);
}
static ERL_NIF_TERM re2_replace(
ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
return SCHEDULE_NIF(
env, "replace", ds_flags, &re2_replace_impl, argc, argv);
}
static ErlNifFunc nif_funcs[] = {
NIF_FUNC_ENTRY("compile", 1, re2_compile),
NIF_FUNC_ENTRY("compile", 2, re2_compile),
NIF_FUNC_ENTRY("match", 2, re2_match),
NIF_FUNC_ENTRY("match", 3, re2_match),
NIF_FUNC_ENTRY("run", 2, re2_match),
NIF_FUNC_ENTRY("run", 3, re2_match),
NIF_FUNC_ENTRY("replace", 3, re2_replace),
NIF_FUNC_ENTRY("replace", 4, re2_replace),
};
static void re2_resource_cleanup(ErlNifEnv*, void* arg)
{
// Release any dynamically allocated memory stored in re2_handle
re2_handle* handle = (re2_handle*)arg;
cleanup_handle(handle);
}
static int on_load(ErlNifEnv* env, void**, ERL_NIF_TERM)
{
ErlNifResourceFlags flags
= (ErlNifResourceFlags)(ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER);
ErlNifResourceType* rt = enif_open_resource_type(
env, nullptr, "re2_resource", &re2_resource_cleanup, flags, nullptr);
if (rt == nullptr)
return -1;
re2_resource_type = rt;
init_atoms(env);
if (have_online_dirty_schedulers()) {
DBG("dirty schedulers: online\n");
ds_flags = DS_MODE;
} else {
DBG("dirty schedulers: offline or unsupported\n");
}
return 0;
}
ERL_NIF_INIT(re2, nif_funcs, &on_load, nullptr, nullptr, nullptr)
} // extern "C"