<commit_before>#include "jsi.h"
#include "jsvalue.h"
#include "jsbuiltin.h"

#define nelem(a) (sizeof (a) / sizeof (a)[0])

#include <regex.h>

int js_RegExp_prototype_exec(js_State *J, int idx, const char *text)
{
	int flags, opts;
	regex_t *prog;
	regmatch_t m[10];
	int i;

	prog = js_toregexp(J, idx, &flags);

	opts = REG_EXTENDED;
	if (flags & JS_REGEXP_I) opts |= REG_ICASE;
	if (flags & JS_REGEXP_M) opts |= REG_NEWLINE;

	// TODO: global and lastIndex

	if (!regexec(prog, text, nelem(m), m, opts)) {
		js_newarray(J);
		for (i = 0; i < nelem(m) && m[i].rm_so >= 0; ++i) {
			js_pushlstring(J, text + m[i].rm_so, m[i].rm_eo - m[i].rm_so);
			js_setindex(J, -2, i);
		}
		return 1;
	}

	js_pushnull(J);
	return 1;
}

void js_newregexp(js_State *J, const char *pattern, int flags)
{
	char msg[256];
	js_Object *obj;
	regex_t *prog;
	int opts, status;

	obj = jsV_newobject(J, JS_CREGEXP, J->RegExp_prototype);

	opts = REG_EXTENDED;
	if (flags & JS_REGEXP_I) opts |= REG_ICASE;
	if (flags & JS_REGEXP_M) opts |= REG_NEWLINE;

	prog = malloc(sizeof (regex_t));
	status = regcomp(prog, pattern, opts);
	if (status) {
		free(prog);
		regerror(status, prog, msg, sizeof msg);
		js_syntaxerror(J, "%s", msg);
	}

	obj->u.r.prog = prog;
	obj->u.r.flags = flags;
	js_pushobject(J, obj);

	js_pushstring(J, pattern);
	js_defproperty(J, -2, "source", JS_READONLY | JS_DONTENUM | JS_DONTDELETE);
	js_pushboolean(J, flags & JS_REGEXP_G);
	js_defproperty(J, -2, "global", JS_READONLY | JS_DONTENUM | JS_DONTDELETE);
	js_pushboolean(J, flags & JS_REGEXP_I);
	js_defproperty(J, -2, "ignoreCase", JS_READONLY | JS_DONTENUM | JS_DONTDELETE);
	js_pushboolean(J, flags & JS_REGEXP_M);
	js_defproperty(J, -2, "multiline", JS_READONLY | JS_DONTENUM | JS_DONTDELETE);

	// TODO: lastIndex
}

static int jsB_new_RegExp(js_State *J, int argc)
{
	const char *pattern;
	int flags;

	if (js_isregexp(J, 1)) {
		if (argc > 1)
			js_typeerror(J, "cannot supply flags when creating one RegExp from another");
		js_toregexp(J, 1, &flags);
		js_getproperty(J, 1, "source");
		pattern = js_tostring(J, -1);
		js_pop(J, 1);
	} else if (js_isundefined(J, 1)) {
		pattern = "";
		flags = 0;
	} else {
		pattern = js_tostring(J, 1);
		flags = 0;
	}

	if (argc > 1) {
		const char *s = js_tostring(J, 2);
		int g = 0, i = 0, m = 0;
		while (*s) {
			if (*s == 'g') ++g;
			else if (*s == 'i') ++i;
			else if (*s == 'm') ++m;
			else js_syntaxerror(J, "invalid regular expression flag: '%c'", *s);
			++s;
		}
		if (g > 1) js_syntaxerror(J, "invalid regular expression flag: 'g'");
		if (i > 1) js_syntaxerror(J, "invalid regular expression flag: 'i'");
		if (m > 1) js_syntaxerror(J, "invalid regular expression flag: 'm'");
		if (g) flags |= JS_REGEXP_G;
		if (i) flags |= JS_REGEXP_I;
		if (m) flags |= JS_REGEXP_M;
	}

	js_newregexp(J, pattern, flags);
	return 1;
}

static int jsB_RegExp(js_State *J, int argc)
{
	if (js_isregexp(J, 1) && argc == 1)
		return 1;
	return jsB_new_RegExp(J, argc);
}

static int Rp_toString(js_State *J, int argc)
{
	const char *source;
	int flags;
	char *out;

	js_Object *self = js_toobject(J, 0);
	if (self->type != JS_CREGEXP)
		js_typeerror(J, "not a regexp");

	flags = self->u.r.flags;

	js_getproperty(J, 0, "source");
	source = js_tostring(J, -1);

	out = malloc(strlen(source) + 6); /* extra space for //gim */
	strcpy(out, "/");
	strcat(out, source);
	strcat(out, "/");
	if (flags & JS_REGEXP_G) strcat(out, "g");
	if (flags & JS_REGEXP_I) strcat(out, "i");
	if (flags & JS_REGEXP_M) strcat(out, "m");

	if (js_try(J)) {
		free(out);
		js_throw(J);
	}
	js_pop(J, 0);
	js_pushstring(J, out);
	js_endtry(J);
	free(out);
	return 1;
}

static int Rp_exec(js_State *J, int argc)
{
	return js_RegExp_prototype_exec(J, 0, js_tostring(J, 1));
}

static int Rp_test(js_State *J, int argc)
{
	int flags;
	regex_t *prog;
	const char *text;

	prog = js_toregexp(J, 0, &flags);
	text = js_tostring(J, 1);

	// TODO: global and lastIndex

	js_pushboolean(J, !regexec(prog, text, 0, NULL, 0));
	return 1;
}

void jsB_initregexp(js_State *J)
{
	js_pushobject(J, J->RegExp_prototype);
	{
		jsB_propf(J, "toString", Rp_toString, 0);
		jsB_propf(J, "test", Rp_test, 0);
		jsB_propf(J, "exec", Rp_exec, 0);
	}
	js_newcconstructor(J, jsB_RegExp, jsB_new_RegExp, 1);
	js_defglobal(J, "RegExp", JS_DONTENUM);
}<commit_msg>Handle global flag in RexExp.exec() and .test().<commit_after>#include "jsi.h"
#include "jsvalue.h"
#include "jsbuiltin.h"

#define nelem(a) (sizeof (a) / sizeof (a)[0])

#include <regex.h>

int js_RegExp_prototype_exec(js_State *J, int idx, const char *text)
{
	js_Object *obj;
	int flags, opts, last;
	regex_t *prog;
	regmatch_t m[10];
	int i;

	prog = js_toregexp(J, idx, &flags);
	obj = js_toobject(J, idx);

	opts = 0;
	if (flags & JS_REGEXP_G) {
		last = obj->u.r.last;
		if (last < 0 || last > strlen(text)) {
			obj->u.r.last = 0;
			js_pushnull(J);
			return 1;
		}
		if (last > 0) {
			text += last;
			opts |= REG_NOTBOL;
		}
	}

	if (!regexec(prog, text, nelem(m), m, opts)) {
		js_newarray(J);
		for (i = 0; i < nelem(m) && m[i].rm_so >= 0; ++i) {
			js_pushlstring(J, text + m[i].rm_so, m[i].rm_eo - m[i].rm_so);
			js_setindex(J, -2, i);
		}
		if (flags & JS_REGEXP_G)
			obj->u.r.last = last + m[0].rm_eo;
		return 1;
	}

	if (flags & JS_REGEXP_G)
		obj->u.r.last = 0;

	js_pushnull(J);
	return 1;
}

void js_newregexp(js_State *J, const char *pattern, int flags)
{
	char msg[256];
	js_Object *obj;
	regex_t *prog;
	int opts, status;

	obj = jsV_newobject(J, JS_CREGEXP, J->RegExp_prototype);

	opts = REG_EXTENDED;
	if (flags & JS_REGEXP_I) opts |= REG_ICASE;
	if (flags & JS_REGEXP_M) opts |= REG_NEWLINE;

	prog = malloc(sizeof (regex_t));
	status = regcomp(prog, pattern, opts);
	if (status) {
		free(prog);
		regerror(status, prog, msg, sizeof msg);
		js_syntaxerror(J, "%s", msg);
	}

	obj->u.r.prog = prog;
	obj->u.r.flags = flags;
	obj->u.r.last = 0;
	js_pushobject(J, obj);

	js_pushstring(J, pattern);
	js_defproperty(J, -2, "source", JS_READONLY | JS_DONTENUM | JS_DONTDELETE);
	js_pushboolean(J, flags & JS_REGEXP_G);
	js_defproperty(J, -2, "global", JS_READONLY | JS_DONTENUM | JS_DONTDELETE);
	js_pushboolean(J, flags & JS_REGEXP_I);
	js_defproperty(J, -2, "ignoreCase", JS_READONLY | JS_DONTENUM | JS_DONTDELETE);
	js_pushboolean(J, flags & JS_REGEXP_M);
	js_defproperty(J, -2, "multiline", JS_READONLY | JS_DONTENUM | JS_DONTDELETE);

	// TODO: lastIndex
}

static int jsB_new_RegExp(js_State *J, int argc)
{
	const char *pattern;
	int flags;

	if (js_isregexp(J, 1)) {
		if (argc > 1)
			js_typeerror(J, "cannot supply flags when creating one RegExp from another");
		js_toregexp(J, 1, &flags);
		js_getproperty(J, 1, "source");
		pattern = js_tostring(J, -1);
		js_pop(J, 1);
	} else if (js_isundefined(J, 1)) {
		pattern = "";
		flags = 0;
	} else {
		pattern = js_tostring(J, 1);
		flags = 0;
	}

	if (argc > 1) {
		const char *s = js_tostring(J, 2);
		int g = 0, i = 0, m = 0;
		while (*s) {
			if (*s == 'g') ++g;
			else if (*s == 'i') ++i;
			else if (*s == 'm') ++m;
			else js_syntaxerror(J, "invalid regular expression flag: '%c'", *s);
			++s;
		}
		if (g > 1) js_syntaxerror(J, "invalid regular expression flag: 'g'");
		if (i > 1) js_syntaxerror(J, "invalid regular expression flag: 'i'");
		if (m > 1) js_syntaxerror(J, "invalid regular expression flag: 'm'");
		if (g) flags |= JS_REGEXP_G;
		if (i) flags |= JS_REGEXP_I;
		if (m) flags |= JS_REGEXP_M;
	}

	js_newregexp(J, pattern, flags);
	return 1;
}

static int jsB_RegExp(js_State *J, int argc)
{
	if (js_isregexp(J, 1) && argc == 1)
		return 1;
	return jsB_new_RegExp(J, argc);
}

static int Rp_toString(js_State *J, int argc)
{
	const char *source;
	int flags;
	char *out;

	js_Object *self = js_toobject(J, 0);
	if (self->type != JS_CREGEXP)
		js_typeerror(J, "not a regexp");

	flags = self->u.r.flags;

	js_getproperty(J, 0, "source");
	source = js_tostring(J, -1);

	out = malloc(strlen(source) + 6); /* extra space for //gim */
	strcpy(out, "/");
	strcat(out, source);
	strcat(out, "/");
	if (flags & JS_REGEXP_G) strcat(out, "g");
	if (flags & JS_REGEXP_I) strcat(out, "i");
	if (flags & JS_REGEXP_M) strcat(out, "m");

	if (js_try(J)) {
		free(out);
		js_throw(J);
	}
	js_pop(J, 0);
	js_pushstring(J, out);
	js_endtry(J);
	free(out);
	return 1;
}

static int Rp_exec(js_State *J, int argc)
{
	return js_RegExp_prototype_exec(J, 0, js_tostring(J, 1));
}

static int Rp_test(js_State *J, int argc)
{
	int flags, opts, last;
	js_Object *obj;
	regmatch_t m[10];
	regex_t *prog;
	const char *text;

	prog = js_toregexp(J, 0, &flags);
	obj = js_toobject(J, 0);
	text = js_tostring(J, 1);

	opts = 0;
	if (flags & JS_REGEXP_G) {
		last = obj->u.r.last;
		if (last < 0 || last > strlen(text)) {
			obj->u.r.last = 0;
			js_pushboolean(J, 0);
			return 1;
		}
		if (last > 0) {
			text += last;
			opts |= REG_NOTBOL;
		}
	}

	if (!regexec(prog, text, nelem(m), m, opts)) {
		if (flags & JS_REGEXP_G)
			obj->u.r.last = last + m[0].rm_eo;
		js_pushboolean(J, 1);
		return 1;
	}

	if (flags & JS_REGEXP_G)
		obj->u.r.last = 0;

	js_pushboolean(J, 0);
	return 1;
}

void jsB_initregexp(js_State *J)
{
	js_pushobject(J, J->RegExp_prototype);
	{
		jsB_propf(J, "toString", Rp_toString, 0);
		jsB_propf(J, "test", Rp_test, 0);
		jsB_propf(J, "exec", Rp_exec, 0);
	}
	js_newcconstructor(J, jsB_RegExp, jsB_new_RegExp, 1);
	js_defglobal(J, "RegExp", JS_DONTENUM);
}
{'fraction_non_alphanumeric': 0.17374146757679182, 'fraction_numerical': 0.013651877133105802, 'mean_word_length': 4.019807280513919, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 42, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3037502', 'n_tokens_mistral': 4509, 'n_tokens_neox': 4197, 'n_words': 1004}