Added some standard C regex implementation. (0.20.0)
This commit is contained in:
parent
63edf97aba
commit
94602f0855
12 changed files with 438 additions and 1 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
project(fun VERSION 0.19.0 LANGUAGES C)
|
project(fun VERSION 0.20.0 LANGUAGES C)
|
||||||
|
|
||||||
set(CMAKE_C_STANDARD 11)
|
set(CMAKE_C_STANDARD 11)
|
||||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
|
|
||||||
38
examples/regex_demo.fun
Normal file
38
examples/regex_demo.fun
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
#!/usr/bin/env fun
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Fun programming language.
|
||||||
|
* https://hanez.org/project/fun/
|
||||||
|
*
|
||||||
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*
|
||||||
|
* Added: 2025-10-04
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Simple demo of Regex stdlib class.
|
||||||
|
|
||||||
|
include <regex.fun>
|
||||||
|
|
||||||
|
r = Regex()
|
||||||
|
|
||||||
|
text = "abc-123-xyz"
|
||||||
|
|
||||||
|
// full match (should be 0)
|
||||||
|
m1 = r.match(text, "[a-z]+-[0-9]+-[a-z]+$")
|
||||||
|
print(m1)
|
||||||
|
|
||||||
|
// search
|
||||||
|
s1 = r.search(text, "[0-9]+")
|
||||||
|
print(s1)
|
||||||
|
|
||||||
|
// replace digits with #
|
||||||
|
out = r.replace(text, "[0-9]", "#")
|
||||||
|
print(out)
|
||||||
|
|
||||||
|
/* Expected output:
|
||||||
|
1
|
||||||
|
{"match": 123, "start": 4, "end": 7, "groups": []}
|
||||||
|
abc-###-xyz
|
||||||
|
*/
|
||||||
35
examples/regex_procedural.fun
Normal file
35
examples/regex_procedural.fun
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
#!/usr/bin/env fun
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Fun programming language.
|
||||||
|
* https://hanez.org/project/fun/
|
||||||
|
*
|
||||||
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*
|
||||||
|
* Added: 2025-10-04
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Procedural regex demo using built-in functions.
|
||||||
|
// Shows internal power without the Regex class wrapper.
|
||||||
|
|
||||||
|
text = "abc-123-xyz"
|
||||||
|
|
||||||
|
// Full string match
|
||||||
|
m1 = regex_match(text, "[a-z]+-[0-9]+-[a-z]+$")
|
||||||
|
print(m1)
|
||||||
|
|
||||||
|
// First search with groups (prints a map)
|
||||||
|
s1 = regex_search(text, "([0-9])([0-9])([0-9])")
|
||||||
|
print(s1)
|
||||||
|
|
||||||
|
// Global replace: replace digits with '#'
|
||||||
|
out = regex_replace(text, "[0-9]", "#")
|
||||||
|
print(out)
|
||||||
|
|
||||||
|
/* Expected output:
|
||||||
|
1
|
||||||
|
{"match": 123, "start": 4, "end": 7, "groups": [1, 2, 3]}
|
||||||
|
abc-###-xyz
|
||||||
|
*/
|
||||||
33
lib/regex.fun
Normal file
33
lib/regex.fun
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the Fun programming language.
|
||||||
|
* https://hanez.org/project/fun/
|
||||||
|
*
|
||||||
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*
|
||||||
|
* Added: 2025-10-04
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Regex stdlib abstraction over VM regex opcodes.
|
||||||
|
// Provides simple methods and sensible defaults.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Methods:
|
||||||
|
- match(text, pattern) -> 1/0 (full match)
|
||||||
|
- search(text, pattern) -> map {"match": str, "start": int, "end": int, "groups": array}
|
||||||
|
- replace(text, pattern, repl) -> string (global)
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Regex()
|
||||||
|
// Full match: returns 1 if the whole string matches the pattern
|
||||||
|
fun match(this, text, pattern)
|
||||||
|
return regex_match(to_string(text), to_string(pattern))
|
||||||
|
|
||||||
|
// First match with groups as array
|
||||||
|
fun search(this, text, pattern)
|
||||||
|
return regex_search(to_string(text), to_string(pattern))
|
||||||
|
|
||||||
|
// Global replace; replacement is literal (no backrefs)
|
||||||
|
fun replace(this, text, pattern, repl)
|
||||||
|
return regex_replace(to_string(text), to_string(pattern), to_string(repl))
|
||||||
|
|
@ -98,6 +98,9 @@ static const char *opcode_name(OpCode op) {
|
||||||
case OP_JOIN: return "JOIN";
|
case OP_JOIN: return "JOIN";
|
||||||
case OP_SUBSTR: return "SUBSTR";
|
case OP_SUBSTR: return "SUBSTR";
|
||||||
case OP_FIND: return "FIND";
|
case OP_FIND: return "FIND";
|
||||||
|
case OP_REGEX_MATCH: return "REGEX_MATCH";
|
||||||
|
case OP_REGEX_SEARCH: return "REGEX_SEARCH";
|
||||||
|
case OP_REGEX_REPLACE: return "REGEX_REPLACE";
|
||||||
case OP_CONTAINS: return "CONTAINS";
|
case OP_CONTAINS: return "CONTAINS";
|
||||||
case OP_INDEX_OF: return "INDEX_OF";
|
case OP_INDEX_OF: return "INDEX_OF";
|
||||||
case OP_CLEAR: return "CLEAR";
|
case OP_CLEAR: return "CLEAR";
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,11 @@ typedef enum {
|
||||||
OP_SUBSTR, // pops len, start, string; pushes string
|
OP_SUBSTR, // pops len, start, string; pushes string
|
||||||
OP_FIND, // pops needle, haystack; pushes int index or -1
|
OP_FIND, // pops needle, haystack; pushes int index or -1
|
||||||
|
|
||||||
|
// regex ops (POSIX)
|
||||||
|
OP_REGEX_MATCH, // pops pattern, string; pushes 1/0 for full match
|
||||||
|
OP_REGEX_SEARCH, // pops pattern, string; pushes map {"match":str, "start":int, "end":int, "groups":array}
|
||||||
|
OP_REGEX_REPLACE, // pops repl, pattern, string; pushes string with global replacements
|
||||||
|
|
||||||
// array utils
|
// array utils
|
||||||
OP_CONTAINS, // pops value, array; pushes 1/0
|
OP_CONTAINS, // pops value, array; pushes 1/0
|
||||||
OP_INDEX_OF, // pops value, array; pushes index or -1
|
OP_INDEX_OF, // pops value, array; pushes index or -1
|
||||||
|
|
|
||||||
33
src/parser.c
33
src/parser.c
|
|
@ -800,6 +800,39 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
|
||||||
free(name);
|
free(name);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
/* regex ops */
|
||||||
|
if (strcmp(name, "regex_match") == 0) {
|
||||||
|
(*pos)++; /* '(' */
|
||||||
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "regex_match expects text"); free(name); return 0; }
|
||||||
|
if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "regex_match expects 2 args"); free(name); return 0; }
|
||||||
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "regex_match expects pattern"); free(name); return 0; }
|
||||||
|
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after regex_match args"); free(name); return 0; }
|
||||||
|
bytecode_add_instruction(bc, OP_REGEX_MATCH, 0);
|
||||||
|
free(name);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (strcmp(name, "regex_search") == 0) {
|
||||||
|
(*pos)++; /* '(' */
|
||||||
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "regex_search expects text"); free(name); return 0; }
|
||||||
|
if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "regex_search expects 2 args"); free(name); return 0; }
|
||||||
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "regex_search expects pattern"); free(name); return 0; }
|
||||||
|
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after regex_search args"); free(name); return 0; }
|
||||||
|
bytecode_add_instruction(bc, OP_REGEX_SEARCH, 0);
|
||||||
|
free(name);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (strcmp(name, "regex_replace") == 0) {
|
||||||
|
(*pos)++; /* '(' */
|
||||||
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "regex_replace expects text"); free(name); return 0; }
|
||||||
|
if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "regex_replace expects 3 args"); free(name); return 0; }
|
||||||
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "regex_replace expects pattern"); free(name); return 0; }
|
||||||
|
if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "regex_replace expects 3 args"); free(name); return 0; }
|
||||||
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "regex_replace expects replacement"); free(name); return 0; }
|
||||||
|
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after regex_replace args"); free(name); return 0; }
|
||||||
|
bytecode_add_instruction(bc, OP_REGEX_REPLACE, 0);
|
||||||
|
free(name);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
/* array utils */
|
/* array utils */
|
||||||
if (strcmp(name, "contains") == 0) {
|
if (strcmp(name, "contains") == 0) {
|
||||||
(*pos)++; /* '(' */
|
(*pos)++; /* '(' */
|
||||||
|
|
|
||||||
3
src/vm.c
3
src/vm.c
|
|
@ -338,6 +338,9 @@ void vm_run(VM *vm, Bytecode *entry) {
|
||||||
#include "vm/pcsc/transmit.c"
|
#include "vm/pcsc/transmit.c"
|
||||||
|
|
||||||
#include "vm/strings/find.c"
|
#include "vm/strings/find.c"
|
||||||
|
#include "vm/strings/regex_match.c"
|
||||||
|
#include "vm/strings/regex_search.c"
|
||||||
|
#include "vm/strings/regex_replace.c"
|
||||||
#include "vm/strings/split.c"
|
#include "vm/strings/split.c"
|
||||||
#include "vm/strings/substr.c"
|
#include "vm/strings/substr.c"
|
||||||
|
|
||||||
|
|
|
||||||
1
src/vm.h
1
src/vm.h
|
|
@ -29,6 +29,7 @@ static const char *opcode_names[] = {
|
||||||
"LEN","PUSH","APOP","SET","INSERT","REMOVE","SLICE",
|
"LEN","PUSH","APOP","SET","INSERT","REMOVE","SLICE",
|
||||||
"TO_NUMBER","TO_STRING","CAST","TYPEOF",
|
"TO_NUMBER","TO_STRING","CAST","TYPEOF",
|
||||||
"SPLIT","JOIN","SUBSTR","FIND",
|
"SPLIT","JOIN","SUBSTR","FIND",
|
||||||
|
"REGEX_MATCH","REGEX_SEARCH","REGEX_REPLACE",
|
||||||
"CONTAINS","INDEX_OF","CLEAR",
|
"CONTAINS","INDEX_OF","CLEAR",
|
||||||
"ENUMERATE","ZIP",
|
"ENUMERATE","ZIP",
|
||||||
"MIN","MAX","CLAMP","ABS","POW","RANDOM_SEED","RANDOM_INT",
|
"MIN","MAX","CLAMP","ABS","POW","RANDOM_SEED","RANDOM_INT",
|
||||||
|
|
|
||||||
57
src/vm/strings/regex_match.c
Normal file
57
src/vm/strings/regex_match.c
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
/**
|
||||||
|
* This file is part of the Fun programming language.
|
||||||
|
* https://hanez.org/project/fun/
|
||||||
|
*
|
||||||
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*
|
||||||
|
* Added: 2025-10-04
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Regex full-match opcode using POSIX regex */
|
||||||
|
#ifdef __unix__
|
||||||
|
#include <regex.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
case OP_REGEX_MATCH: {
|
||||||
|
Value pattern = pop_value(vm);
|
||||||
|
Value str = pop_value(vm);
|
||||||
|
if (str.type != VAL_STRING || pattern.type != VAL_STRING) {
|
||||||
|
fprintf(stderr, "Runtime type error: REGEX_MATCH expects (string, string)\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
#ifndef __unix__
|
||||||
|
/* Not supported on non-UNIX: return 0 gracefully */
|
||||||
|
free_value(pattern);
|
||||||
|
int truth = 0;
|
||||||
|
free_value(str);
|
||||||
|
push_value(vm, make_int(truth));
|
||||||
|
break;
|
||||||
|
#else
|
||||||
|
regex_t rx;
|
||||||
|
int rc = regcomp(&rx, pattern.s ? pattern.s : "", REG_EXTENDED);
|
||||||
|
if (rc != 0) {
|
||||||
|
/* invalid regex -> false */
|
||||||
|
free_value(pattern);
|
||||||
|
free_value(str);
|
||||||
|
push_value(vm, make_int(0));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
regmatch_t m;
|
||||||
|
int ok = regexec(&rx, str.s ? str.s : "", 1, &m, 0) == 0;
|
||||||
|
int truth = 0;
|
||||||
|
if (ok) {
|
||||||
|
/* full match means the match spans whole string */
|
||||||
|
if (m.rm_so == 0 && str.s) {
|
||||||
|
size_t slen = strlen(str.s);
|
||||||
|
truth = (m.rm_eo == (regoff_t)slen) ? 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
regfree(&rx);
|
||||||
|
free_value(pattern);
|
||||||
|
free_value(str);
|
||||||
|
push_value(vm, make_int(truth));
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
116
src/vm/strings/regex_replace.c
Normal file
116
src/vm/strings/regex_replace.c
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
/**
|
||||||
|
* This file is part of the Fun programming language.
|
||||||
|
* https://hanez.org/project/fun/
|
||||||
|
*
|
||||||
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*
|
||||||
|
* Added: 2025-10-04
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Regex global replace opcode using POSIX regex */
|
||||||
|
#ifdef __unix__
|
||||||
|
#include <regex.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
case OP_REGEX_REPLACE: {
|
||||||
|
Value repl = pop_value(vm);
|
||||||
|
Value pattern = pop_value(vm);
|
||||||
|
Value str = pop_value(vm);
|
||||||
|
if (str.type != VAL_STRING || pattern.type != VAL_STRING || repl.type != VAL_STRING) {
|
||||||
|
fprintf(stderr, "Runtime type error: REGEX_REPLACE expects (string, string, string)\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
#ifndef __unix__
|
||||||
|
/* Not supported: return original string */
|
||||||
|
Value out = make_string(str.s ? str.s : "");
|
||||||
|
free_value(repl);
|
||||||
|
free_value(pattern);
|
||||||
|
free_value(str);
|
||||||
|
push_value(vm, out);
|
||||||
|
break;
|
||||||
|
#else
|
||||||
|
regex_t rx;
|
||||||
|
int rc = regcomp(&rx, pattern.s ? pattern.s : "", REG_EXTENDED);
|
||||||
|
if (rc != 0) {
|
||||||
|
/* invalid regex -> return original */
|
||||||
|
Value out = make_string(str.s ? str.s : "");
|
||||||
|
free_value(repl);
|
||||||
|
free_value(pattern);
|
||||||
|
free_value(str);
|
||||||
|
push_value(vm, out);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *s = str.s ? str.s : "";
|
||||||
|
const char *r = repl.s ? repl.s : "";
|
||||||
|
|
||||||
|
size_t out_cap = strlen(s) + 1;
|
||||||
|
char *outbuf = (char*)malloc(out_cap);
|
||||||
|
size_t out_len = 0;
|
||||||
|
size_t pos = 0;
|
||||||
|
|
||||||
|
enum { MAX_CAP = 16 };
|
||||||
|
regmatch_t caps[MAX_CAP];
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
if (regexec(&rx, s + pos, MAX_CAP, caps, 0) != 0) {
|
||||||
|
/* no more matches: append the rest */
|
||||||
|
size_t rest = strlen(s + pos);
|
||||||
|
if (out_len + rest + 1 > out_cap) {
|
||||||
|
out_cap = out_len + rest + 1;
|
||||||
|
outbuf = (char*)realloc(outbuf, out_cap);
|
||||||
|
}
|
||||||
|
memcpy(outbuf + out_len, s + pos, rest + 1);
|
||||||
|
out_len += rest;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int mstart = (int)caps[0].rm_so;
|
||||||
|
int mend = (int)caps[0].rm_eo;
|
||||||
|
if (mstart < 0 || mend < mstart) {
|
||||||
|
/* Shouldn't happen, avoid infinite loop */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* append prefix */
|
||||||
|
size_t pre_len = (size_t)mstart;
|
||||||
|
if (out_len + pre_len + 1 > out_cap) {
|
||||||
|
out_cap = (out_len + pre_len + 1) * 2;
|
||||||
|
outbuf = (char*)realloc(outbuf, out_cap);
|
||||||
|
}
|
||||||
|
memcpy(outbuf + out_len, s + pos, pre_len);
|
||||||
|
out_len += pre_len;
|
||||||
|
|
||||||
|
/* append replacement (no backref expansion for simplicity) */
|
||||||
|
size_t rlen = strlen(r);
|
||||||
|
if (out_len + rlen + 1 > out_cap) {
|
||||||
|
out_cap = (out_len + rlen + 1) * 2;
|
||||||
|
outbuf = (char*)realloc(outbuf, out_cap);
|
||||||
|
}
|
||||||
|
memcpy(outbuf + out_len, r, rlen);
|
||||||
|
out_len += rlen;
|
||||||
|
|
||||||
|
/* advance */
|
||||||
|
pos += (size_t)mend;
|
||||||
|
if (mend == 0) { /* prevent zero-length match infinite loop */
|
||||||
|
if (pos < strlen(s)) {
|
||||||
|
if (out_len + 1 > out_cap) { out_cap = out_len + 2; outbuf = (char*)realloc(outbuf, out_cap);}
|
||||||
|
outbuf[out_len++] = s[pos++];
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value out = make_string(outbuf ? outbuf : "");
|
||||||
|
if (outbuf) free(outbuf);
|
||||||
|
regfree(&rx);
|
||||||
|
free_value(repl);
|
||||||
|
free_value(pattern);
|
||||||
|
free_value(str);
|
||||||
|
push_value(vm, out);
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
113
src/vm/strings/regex_search.c
Normal file
113
src/vm/strings/regex_search.c
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
/**
|
||||||
|
* This file is part of the Fun programming language.
|
||||||
|
* https://hanez.org/project/fun/
|
||||||
|
*
|
||||||
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*
|
||||||
|
* Added: 2025-10-04
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Regex search (first match) opcode using POSIX regex */
|
||||||
|
#ifdef __unix__
|
||||||
|
#include <regex.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
case OP_REGEX_SEARCH: {
|
||||||
|
Value pattern = pop_value(vm);
|
||||||
|
Value str = pop_value(vm);
|
||||||
|
if (str.type != VAL_STRING || pattern.type != VAL_STRING) {
|
||||||
|
fprintf(stderr, "Runtime type error: REGEX_SEARCH expects (string, string)\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
#ifndef __unix__
|
||||||
|
/* Return default empty result on unsupported platforms */
|
||||||
|
Value m = make_map_empty();
|
||||||
|
(void)map_set(&m, "match", make_string(""));
|
||||||
|
(void)map_set(&m, "start", make_int(-1));
|
||||||
|
(void)map_set(&m, "end", make_int(-1));
|
||||||
|
Value emptyArr = make_array_from_values(NULL, 0);
|
||||||
|
(void)map_set(&m, "groups", emptyArr);
|
||||||
|
free_value(pattern);
|
||||||
|
free_value(str);
|
||||||
|
push_value(vm, m);
|
||||||
|
break;
|
||||||
|
#else
|
||||||
|
regex_t rx;
|
||||||
|
int rc = regcomp(&rx, pattern.s ? pattern.s : "", REG_EXTENDED);
|
||||||
|
if (rc != 0) {
|
||||||
|
/* invalid regex -> empty result */
|
||||||
|
Value m = make_map_empty();
|
||||||
|
(void)map_set(&m, "match", make_string(""));
|
||||||
|
(void)map_set(&m, "start", make_int(-1));
|
||||||
|
(void)map_set(&m, "end", make_int(-1));
|
||||||
|
Value emptyArr = make_array_from_values(NULL, 0);
|
||||||
|
(void)map_set(&m, "groups", emptyArr);
|
||||||
|
free_value(pattern);
|
||||||
|
free_value(str);
|
||||||
|
push_value(vm, m);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* capture up to, say, 10 groups (including whole match) */
|
||||||
|
enum { MAX_CAP = 16 };
|
||||||
|
regmatch_t caps[MAX_CAP];
|
||||||
|
int ok = regexec(&rx, str.s ? str.s : "", MAX_CAP, caps, 0) == 0;
|
||||||
|
Value outMap = make_map_empty();
|
||||||
|
if (!ok) {
|
||||||
|
(void)map_set(&outMap, "match", make_string(""));
|
||||||
|
(void)map_set(&outMap, "start", make_int(-1));
|
||||||
|
(void)map_set(&outMap, "end", make_int(-1));
|
||||||
|
Value emptyArr = make_array_from_values(NULL, 0);
|
||||||
|
(void)map_set(&outMap, "groups", emptyArr);
|
||||||
|
} else {
|
||||||
|
int s = (int)caps[0].rm_so;
|
||||||
|
int e = (int)caps[0].rm_eo;
|
||||||
|
char *matchStr = NULL;
|
||||||
|
if (str.s && s >= 0 && e >= s) {
|
||||||
|
int len = e - s;
|
||||||
|
matchStr = (char*)malloc((size_t)len + 1);
|
||||||
|
if (matchStr) { memcpy(matchStr, str.s + s, (size_t)len); matchStr[len] = '\0'; }
|
||||||
|
}
|
||||||
|
(void)map_set(&outMap, "match", make_string(matchStr ? matchStr : ""));
|
||||||
|
if (matchStr) free(matchStr);
|
||||||
|
(void)map_set(&outMap, "start", make_int(s));
|
||||||
|
(void)map_set(&outMap, "end", make_int(e));
|
||||||
|
/* groups 1..n */
|
||||||
|
Value groupsArr = make_array_from_values(NULL, 0);
|
||||||
|
/* Count groups available */
|
||||||
|
int groupCount = 0;
|
||||||
|
for (int i = 1; i < MAX_CAP; ++i) {
|
||||||
|
if (caps[i].rm_so == -1 || caps[i].rm_eo == -1) break;
|
||||||
|
groupCount++;
|
||||||
|
}
|
||||||
|
if (groupCount > 0) {
|
||||||
|
Value *vals = (Value*)calloc((size_t)groupCount, sizeof(Value));
|
||||||
|
int vi = 0;
|
||||||
|
for (int i = 1; i <= groupCount; ++i) {
|
||||||
|
int gs = (int)caps[i].rm_so;
|
||||||
|
int ge = (int)caps[i].rm_eo;
|
||||||
|
char *gstr = NULL;
|
||||||
|
if (str.s && gs >= 0 && ge >= gs) {
|
||||||
|
int gl = ge - gs;
|
||||||
|
gstr = (char*)malloc((size_t)gl + 1);
|
||||||
|
if (gstr) { memcpy(gstr, str.s + gs, (size_t)gl); gstr[gl] = '\0'; }
|
||||||
|
}
|
||||||
|
vals[vi++] = make_string(gstr ? gstr : "");
|
||||||
|
if (gstr) free(gstr);
|
||||||
|
}
|
||||||
|
groupsArr = make_array_from_values(vals, groupCount);
|
||||||
|
for (int i = 0; i < groupCount; ++i) free_value(vals[i]);
|
||||||
|
free(vals);
|
||||||
|
}
|
||||||
|
(void)map_set(&outMap, "groups", groupsArr);
|
||||||
|
}
|
||||||
|
regfree(&rx);
|
||||||
|
free_value(pattern);
|
||||||
|
free_value(str);
|
||||||
|
push_value(vm, outMap);
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue