Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions c_bridges/regex-bridge.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
#include <stdlib.h>
#include <string.h>

extern void *GC_malloc(size_t);
extern void *GC_malloc_atomic(size_t);

void *cs_regex_alloc(void) {
return malloc(sizeof(regex_t));
}
Expand Down Expand Up @@ -32,3 +35,35 @@ void cs_regex_free(void *preg) {
free(preg);
}
}

char *cs_regex_exec_dyn(void *preg, const char *str, int max_groups) {
regmatch_t *pmatch = (regmatch_t *)calloc((size_t)max_groups, sizeof(regmatch_t));
if (!pmatch) return NULL;

if (regexec((regex_t *)preg, str, (size_t)max_groups, pmatch, 0) != 0) {
free(pmatch);
return NULL;
}

int count = 0;
for (int i = 0; i < max_groups; i++) {
if (pmatch[i].rm_so < 0) break;
count++;
}

char **strings = (char **)GC_malloc((size_t)count * sizeof(char *));
for (int i = 0; i < count; i++) {
int slen = pmatch[i].rm_eo - pmatch[i].rm_so;
char *s = (char *)GC_malloc_atomic((size_t)(slen + 1));
if (slen > 0) strncpy(s, str + pmatch[i].rm_so, (size_t)slen);
s[slen] = '\0';
strings[i] = s;
}
free(pmatch);

char *arr = (char *)GC_malloc(16);
*((char ***)arr) = strings;
*((int *)(arr + 8)) = count;
*((int *)(arr + 12)) = count;
return arr;
}
16 changes: 16 additions & 0 deletions src/codegen/expressions/method-calls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,13 @@ export class MethodCallGenerator {
}
}

if (method === "execDyn") {
const isRegex = this.ctx.isRegexExpression(expr.object);
if (isRegex) {
return this.handleRegexExecDyn(expr, params);
}
}

if (method === "isFile" || method === "isDirectory") {
let statI8Ptr: string | null = null;

Expand Down Expand Up @@ -1420,6 +1427,15 @@ export class MethodCallGenerator {
return this.ctx.regexGen.generateRegexMatch(regexPtr, strPtr, numGroups);
}

private handleRegexExecDyn(expr: MethodCallNode, params: string[]): string {
if (expr.args.length !== 1) {
return this.ctx.emitError(`execDyn() expects 1 argument, got ${expr.args.length}`, expr.loc);
}
const regexPtr = this.ctx.generateExpression(expr.object, params);
const strPtr = this.ctx.generateExpression(expr.args[0], params);
return this.ctx.regexGen.generateRegexExecDyn(regexPtr, strPtr);
}

private throwUnsupportedMethodError(
method: string,
_objectType?: string,
Expand Down
2 changes: 2 additions & 0 deletions src/codegen/infrastructure/generator-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export interface IRegexGenerator {
generateRegexTest(regexPtr: string, testStr: string): string;
generateRegexMatch(regexPtr: string, testStr: string, numGroups: number): string;
generateRegexCompileRuntime(patternPtr: string, cflags: number): string;
generateRegexExecDyn(regexPtr: string, testStr: string): string;
}

export interface IControlFlowGenerator {
Expand Down Expand Up @@ -1823,6 +1824,7 @@ export class MockGeneratorContext implements IGeneratorContext {
"%mock_regex_match",
generateRegexCompileRuntime: (_patternPtr: string, _cflags: number): string =>
"%mock_regex_compile_runtime",
generateRegexExecDyn: (_regexPtr: string, _testStr: string): string => "%mock_regex_exec_dyn",
};
controlFlowGen: IControlFlowGenerator = {
generateLogicalOp: (
Expand Down
1 change: 1 addition & 0 deletions src/codegen/infrastructure/llvm-declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export function getLLVMDeclarations(config?: DeclConfig): string {
ir += "declare i64 @cs_pmatch_start(i8*, i32)\n";
ir += "declare i64 @cs_pmatch_end(i8*, i32)\n";
ir += "declare void @cs_regex_free(i8*)\n";
ir += "declare i8* @cs_regex_exec_dyn(i8*, i8*, i32)\n";
ir += "\n";

// child_process bridge — %SpawnSyncResult = { stdout: i8*, stderr: i8*, status: double }
Expand Down
7 changes: 5 additions & 2 deletions src/codegen/infrastructure/type-inference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ export class TypeInference {
return this.ctx.typeContext.booleanType;
}

if (method === "match" || method === "exec") {
if (method === "match" || method === "exec" || method === "execDyn") {
return this.ctx.typeContext.getArrayType("string");
}

Expand Down Expand Up @@ -2142,7 +2142,10 @@ export class TypeInference {
return true;
}
}
if (methodExpr.method === "exec" && this.isRegexExpression(methodExpr.object)) {
if (
(methodExpr.method === "exec" || methodExpr.method === "execDyn") &&
this.isRegexExpression(methodExpr.object)
) {
return true;
}
if (
Expand Down
11 changes: 11 additions & 0 deletions src/codegen/types/objects/regex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,17 @@ export class RegexGenerator {
return result;
}

generateRegexExecDyn(regexPtr: string, testStr: string): string {
this.ctx.setUsesRegex(true);
const result = this.ctx.emitCall(
"i8*",
"@cs_regex_exec_dyn",
`i8* ${regexPtr}, i8* ${testStr}, i32 64`,
);
this.ctx.setVariableType(result, "i8*");
return result;
}

// Clean up regex resources
generateRegexFree(regexPtr: string): void {
this.ctx.emitCallVoid("@cs_regex_free", `i8* ${regexPtr}`);
Expand Down
31 changes: 31 additions & 0 deletions tests/fixtures/regex/regex-exec-dyn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// @test-description: dynamic regex exec returns string array

function testExecDyn(): void {
const re = new RegExp("([a-z]+)/([0-9]+)");
const m1 = re.execDyn("rooms/42");
if (m1 === null) {
console.log("FAIL: expected match");
process.exit(1);
}
if (m1[0] !== "rooms/42") {
console.log("FAIL: full match wrong");
process.exit(1);
}
if (m1[1] !== "rooms") {
console.log("FAIL: group 1 wrong");
process.exit(1);
}
if (m1[2] !== "42") {
console.log("FAIL: group 2 wrong");
process.exit(1);
}

const m2 = re.execDyn("nope");
if (m2 !== null) {
console.log("FAIL: expected no match");
process.exit(1);
}

console.log("TEST_PASSED");
}
testExecDyn();
Loading