Added PCRE2 support. (0.29.0)
This commit is contained in:
parent
5c3d4f1247
commit
d9804d9bf8
15 changed files with 596 additions and 3 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
project(fun VERSION 0.28.0 LANGUAGES C)
|
project(fun VERSION 0.29.0 LANGUAGES C)
|
||||||
|
|
||||||
set(CMAKE_C_STANDARD 99)
|
set(CMAKE_C_STANDARD 99)
|
||||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
|
@ -65,6 +65,28 @@ if(FUN_WITH_JSON)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Optional PCRE2 support
|
||||||
|
option(FUN_WITH_PCRE2 "Enable PCRE2 (Perl Compatible Regular Expressions) support" OFF)
|
||||||
|
set(PCRE2_INCLUDE_DIRS "")
|
||||||
|
set(PCRE2_LINK_LIBS "")
|
||||||
|
if(FUN_WITH_PCRE2)
|
||||||
|
message(STATUS "Building with PCRE2 support")
|
||||||
|
add_definitions(-DFUN_WITH_PCRE2)
|
||||||
|
# Try pkg-config first
|
||||||
|
find_package(PkgConfig QUIET)
|
||||||
|
if(PKG_CONFIG_FOUND)
|
||||||
|
pkg_check_modules(PCRE2 QUIET libpcre2-8)
|
||||||
|
endif()
|
||||||
|
if(PCRE2_FOUND)
|
||||||
|
list(APPEND PCRE2_INCLUDE_DIRS ${PCRE2_INCLUDE_DIRS} ${PCRE2_INCLUDE_DIRS})
|
||||||
|
list(APPEND PCRE2_LINK_LIBS ${PCRE2_LINK_LIBS} ${PCRE2_LIBRARIES})
|
||||||
|
include_directories(${PCRE2_INCLUDE_DIRS})
|
||||||
|
else()
|
||||||
|
# Fallback: common defaults
|
||||||
|
list(APPEND PCRE2_LINK_LIBS pcre2-8)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
# Debug option to enable verbose parser/VM logging
|
# Debug option to enable verbose parser/VM logging
|
||||||
option(FUN_DEBUG "Enable extra debug logging in Fun" OFF)
|
option(FUN_DEBUG "Enable extra debug logging in Fun" OFF)
|
||||||
|
|
||||||
|
|
@ -110,6 +132,14 @@ if(JSONC_LINK_LIBS)
|
||||||
target_link_libraries(fun_core PUBLIC ${JSONC_LINK_LIBS})
|
target_link_libraries(fun_core PUBLIC ${JSONC_LINK_LIBS})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# pcre2 include and link (if enabled)
|
||||||
|
if(PCRE2_INCLUDE_DIRS)
|
||||||
|
target_include_directories(fun_core PRIVATE ${PCRE2_INCLUDE_DIRS})
|
||||||
|
endif()
|
||||||
|
if(PCRE2_LINK_LIBS)
|
||||||
|
target_link_libraries(fun_core PUBLIC ${PCRE2_LINK_LIBS})
|
||||||
|
endif()
|
||||||
|
|
||||||
# Link threads if available on UNIX
|
# Link threads if available on UNIX
|
||||||
if(Threads_FOUND)
|
if(Threads_FOUND)
|
||||||
target_link_libraries(fun_core PUBLIC Threads::Threads)
|
target_link_libraries(fun_core PUBLIC Threads::Threads)
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ Fun is and will ever be 100% free under the terms of the [Apache-2.0 License](ht
|
||||||
- [JSON](https://www.json.org/) support builtin using [json-c](https://github.com/json-c/json-c) (optional) ☑
|
- [JSON](https://www.json.org/) support builtin using [json-c](https://github.com/json-c/json-c) (optional) ☑
|
||||||
- [ODBC](https://learn.microsoft.com/en-us/sql/odbc/reference/odbc-overview?view=sql-server-ver16) support builtin for flexible database connectivity using [unixODBC](https://www.unixodbc.org/) (optional) ☐
|
- [ODBC](https://learn.microsoft.com/en-us/sql/odbc/reference/odbc-overview?view=sql-server-ver16) support builtin for flexible database connectivity using [unixODBC](https://www.unixodbc.org/) (optional) ☐
|
||||||
- [PC/SC](https://pcscworkgroup.com/) smart card support builtin using [PCSC lite](https://pcsclite.apdu.fr/) (optional) ☑
|
- [PC/SC](https://pcscworkgroup.com/) smart card support builtin using [PCSC lite](https://pcsclite.apdu.fr/) (optional) ☑
|
||||||
- [PCRE2](https://pcre2project.github.io/pcre2/) support builtin for Perl-Compatible Regular Expressions (optional) ☐
|
- [PCRE2](https://pcre2project.github.io/pcre2/) support builtin for Perl-Compatible Regular Expressions (optional) ☑
|
||||||
- [SQLite](https://sqlite.org/) support builtin (optional) ☐
|
- [SQLite](https://sqlite.org/) support builtin (optional) ☐
|
||||||
- [Tk](https://www.tcl-lang.org/) support builtin for GUI application development (optional) ☐
|
- [Tk](https://www.tcl-lang.org/) support builtin for GUI application development (optional) ☐
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ Build:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Note: Every -D flag must be of the form NAME=VALUE (e.g., -DFUN_WITH_REPL=ON)
|
# Note: Every -D flag must be of the form NAME=VALUE (e.g., -DFUN_WITH_REPL=ON)
|
||||||
cmake -S . -B build -DFUN_DEBUG=OFF -DFUN_WITH_PCSC=OFF -DFUN_WITH_REPL=ON -DFUN_WITH_JSON=ON
|
cmake -S . -B build -DFUN_DEBUG=OFF -DFUN_WITH_PCSC=OFF -DFUN_WITH_REPL=ON -DFUN_WITH_JSON=ON -DFUN_WITH_PCRE2=ON
|
||||||
cmake --build build --target fun
|
cmake --build build --target fun
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -86,6 +86,7 @@ All CMake options must be passed as -DNAME=VALUE:
|
||||||
|
|
||||||
- FUN_DEBUG=ON|OFF — verbose debug logging in the VM (default OFF)
|
- FUN_DEBUG=ON|OFF — verbose debug logging in the VM (default OFF)
|
||||||
- FUN_WITH_REPL=ON|OFF — enable the interactive REPL (default ON)
|
- FUN_WITH_REPL=ON|OFF — enable the interactive REPL (default ON)
|
||||||
|
- FUN_WITH_PCRE2=ON|OFF — enable PCRE2 Perl-Compatible Regular Expressions support (default OFF)
|
||||||
- FUN_WITH_PCSC=ON|OFF — enable PC/SC smart card support (default OFF)
|
- FUN_WITH_PCSC=ON|OFF — enable PC/SC smart card support (default OFF)
|
||||||
- FUN_WITH_JSON=ON|OFF — enable JSON support via json-c (default OFF)
|
- FUN_WITH_JSON=ON|OFF — enable JSON support via json-c (default OFF)
|
||||||
|
|
||||||
|
|
|
||||||
33
examples/extra/pcre2_demo.fun
Normal file
33
examples/extra/pcre2_demo.fun
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
#!/usr/bin/env fun
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Fun programming language.
|
||||||
|
* https://fun-lang.xyz/
|
||||||
|
*
|
||||||
|
* 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-11-25
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
include <regex/pcre2.fun>
|
||||||
|
|
||||||
|
rx = Pcre2()
|
||||||
|
|
||||||
|
text = "E-mails: one@example.com, Two@Example.COM; invalid: x@y"
|
||||||
|
pattern = "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
|
||||||
|
|
||||||
|
print("Has email? ", rx.test(pattern, text, rx.i()))
|
||||||
|
|
||||||
|
first = rx.match(pattern, text, rx.i())
|
||||||
|
if first != nil {
|
||||||
|
print("First: ", first["full"])
|
||||||
|
}
|
||||||
|
|
||||||
|
all = rx.find_all(pattern, text, rx.i())
|
||||||
|
for m in all {
|
||||||
|
print("Found: ", m["full"])
|
||||||
|
}
|
||||||
|
*/
|
||||||
140
examples/pcre2_opcodes.fun
Normal file
140
examples/pcre2_opcodes.fun
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
#!/usr/bin/env fun
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Fun programming language.
|
||||||
|
* https://fun-lang.xyz/
|
||||||
|
*
|
||||||
|
* 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-11-25
|
||||||
|
*/
|
||||||
|
|
||||||
|
// PCRE2 example using VM builtins directly (no class wrapper)
|
||||||
|
// Requires building Fun with -DFUN_WITH_PCRE2=ON
|
||||||
|
|
||||||
|
print("-- PCRE2 builtins (no class) --")
|
||||||
|
|
||||||
|
pattern = "(\\w+)" // capture a word
|
||||||
|
text = "Hello 123 world"
|
||||||
|
|
||||||
|
// Flags: 1=I, 2=M, 4=S, 8=U (UTF), 16=X; we’ll use UTF by default
|
||||||
|
flags = 8
|
||||||
|
|
||||||
|
print("test:")
|
||||||
|
print(pcre2_test(pattern, text, flags))
|
||||||
|
|
||||||
|
m = pcre2_match(pattern, text, flags)
|
||||||
|
if (m != nil)
|
||||||
|
print("first full:")
|
||||||
|
print(m["full"])
|
||||||
|
print("span:")
|
||||||
|
print(m["start"])
|
||||||
|
print("..")
|
||||||
|
print(m["end"])
|
||||||
|
print("groups count:")
|
||||||
|
print(len(m["groups"]))
|
||||||
|
|
||||||
|
all = pcre2_findall("\\w+", text, flags)
|
||||||
|
for x in all
|
||||||
|
print("all:")
|
||||||
|
print(x["full"])
|
||||||
|
print("@")
|
||||||
|
print(x["start"])
|
||||||
|
print("..")
|
||||||
|
print(x["end"])
|
||||||
|
|
||||||
|
print("")
|
||||||
|
print("-- More regex demos --")
|
||||||
|
|
||||||
|
// Helper: OR flags (uses VM bor opcode)
|
||||||
|
fun OR(a, b)
|
||||||
|
return bor(a, b)
|
||||||
|
|
||||||
|
// Demo 1: E-mail extraction (case-insensitive)
|
||||||
|
email_text = "E-mails: one@example.com, Two@Example.COM; invalid: x@y"
|
||||||
|
email_pat = "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
|
||||||
|
email_flags = OR(flags, 1) // UTF | I
|
||||||
|
print("Emails (findall):")
|
||||||
|
emails = pcre2_findall(email_pat, email_text, email_flags)
|
||||||
|
for e in emails
|
||||||
|
print(e["full"])
|
||||||
|
|
||||||
|
// Demo 2: URLs (very simple, for demo purposes)
|
||||||
|
url_text = "See http://example.com and https://fun-lang.xyz/docs?x=1#top"
|
||||||
|
url_pat = "https?://[A-Za-z0-9._~:/?#[@]!$&'()*+,;=%-]+"
|
||||||
|
print("URLs:")
|
||||||
|
for u in pcre2_findall(url_pat, url_text, flags)
|
||||||
|
print(u["full"])
|
||||||
|
|
||||||
|
// Demo 3: IPv4 addresses
|
||||||
|
ip_text = "ping 8.8.8.8 and 192.168.0.1; not 999.999.999.999"
|
||||||
|
ip_pat = "(?:(?:25[0-5]|2[0-4]\\d|1?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|1?\\d?\\d)"
|
||||||
|
print("IPv4:")
|
||||||
|
for ip in pcre2_findall(ip_pat, ip_text, flags)
|
||||||
|
print(ip["full"])
|
||||||
|
|
||||||
|
// Demo 4: Dates (YYYY-MM-DD)
|
||||||
|
date_text = "Born 1999-12-31, updated 2025-11-25, bad 2025-13-40"
|
||||||
|
date_pat = "(\\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])"
|
||||||
|
print("Dates (with groups Y/M/D):")
|
||||||
|
for d in pcre2_findall(date_pat, date_text, flags)
|
||||||
|
print(d["full"])
|
||||||
|
print(join(d["groups"], "/"))
|
||||||
|
|
||||||
|
// Demo 5: Hex colors (#RRGGBB)
|
||||||
|
color_text = "Palette: #FF00FF, #1a2b3c, not #abcd or #12345g"
|
||||||
|
color_pat = "#[0-9A-Fa-f]{6}"
|
||||||
|
print("Hex colors:")
|
||||||
|
for c in pcre2_findall(color_pat, color_text, flags)
|
||||||
|
print(c["full"])
|
||||||
|
|
||||||
|
// Demo 6: Quoted strings with escapes
|
||||||
|
q_text = 'say "hi there" and "indented" plus "quote\\"inside"'
|
||||||
|
q_pat = '"([^"\\\\]|\\\\.)*"'
|
||||||
|
print("Quoted strings (with escapes):")
|
||||||
|
for q in pcre2_findall(q_pat, q_text, flags)
|
||||||
|
print(q["full"])
|
||||||
|
|
||||||
|
// Demo 7: Multiline anchors with /m (M flag)
|
||||||
|
ml_text = "first line\nSecond line\nthird"
|
||||||
|
ml_pat = "^(\\w+)"
|
||||||
|
ml_flags = OR(flags, 2) // UTF | M
|
||||||
|
print("Multiline ^ anchors (first token of each line):")
|
||||||
|
for ml in pcre2_findall(ml_pat, ml_text, ml_flags)
|
||||||
|
print(ml["full"])
|
||||||
|
|
||||||
|
// Demo 8: Dotall vs non-dotall
|
||||||
|
ds_text = "BEGIN\nline1\nline2\nEND"
|
||||||
|
pat_nd = "BEGIN.*END" // default: . does not match newlines
|
||||||
|
pat_ds = "BEGIN.*END" // with DOTALL, it does
|
||||||
|
print("Dotall OFF (should fail):")
|
||||||
|
print(pcre2_test(pat_nd, ds_text, flags))
|
||||||
|
print("Dotall ON (should match):")
|
||||||
|
print(pcre2_test(pat_ds, ds_text, OR(flags, 4)))
|
||||||
|
|
||||||
|
// Demo 9: Word boundaries and case-insensitive find
|
||||||
|
wb_text = "The theater and the THE can differ."
|
||||||
|
wb_pat = "\\bthe\\b"
|
||||||
|
print("Word boundary, case-insensitive:")
|
||||||
|
for w in pcre2_findall(wb_pat, wb_text, OR(flags, 1))
|
||||||
|
print(w["full"])
|
||||||
|
|
||||||
|
// Demo 10: Lookahead — word followed by number
|
||||||
|
la_text = "foo 123, bar, baz 9"
|
||||||
|
la_pat = "\\w+(?=\\s+\\d+)"
|
||||||
|
print("Lookahead (word before number):")
|
||||||
|
for a in pcre2_findall(la_pat, la_text, flags)
|
||||||
|
print(a["full"])
|
||||||
|
|
||||||
|
// Demo 11: Non-greedy vs greedy
|
||||||
|
ng_text = "<a>one</a><a>two</a>"
|
||||||
|
greedy = "<a>.*</a>"
|
||||||
|
lazy = "<a>.*?</a>"
|
||||||
|
print("Greedy:")
|
||||||
|
for g in pcre2_findall(greedy, ng_text, OR(flags, 4)) // DOTALL ensures '.' covers any
|
||||||
|
print(g["full"])
|
||||||
|
print("Non-greedy:")
|
||||||
|
for l in pcre2_findall(lazy, ng_text, OR(flags, 4))
|
||||||
|
print(l["full"])
|
||||||
32
examples/pcre2_showcase.fun
Normal file
32
examples/pcre2_showcase.fun
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
#!/usr/bin/env fun
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Fun programming language.
|
||||||
|
* https://fun-lang.xyz/
|
||||||
|
*
|
||||||
|
* 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-11-25
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
include <regex/pcre2.fun>
|
||||||
|
|
||||||
|
re = PCRE2()
|
||||||
|
|
||||||
|
print("Testing PCRE2 showcase...")
|
||||||
|
|
||||||
|
print(re.test("\\d+", "Order #1234"))
|
||||||
|
|
||||||
|
m = re.match("(\\w+)", "hello WORLD", re.i())
|
||||||
|
if m != nil {
|
||||||
|
print(m["full"]) // hello
|
||||||
|
print(len(m["groups"]))
|
||||||
|
}
|
||||||
|
|
||||||
|
for x in re.find_all("[a-z]+", "One two THREE four", re.i()) {
|
||||||
|
print(x["full"]) // one two four
|
||||||
|
}
|
||||||
|
*/
|
||||||
42
lib/regex/pcre2.fun
Normal file
42
lib/regex/pcre2.fun
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
#!/usr/bin/env fun
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Fun programming language.
|
||||||
|
* https://fun-lang.xyz/
|
||||||
|
*
|
||||||
|
* 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-11-25
|
||||||
|
*/
|
||||||
|
|
||||||
|
// PCRE2 stdlib abstraction wrapping VM pcre2_* builtins.
|
||||||
|
// Provides a small class with flags and user-friendly methods.
|
||||||
|
|
||||||
|
class PCRE2()
|
||||||
|
fun i(this)
|
||||||
|
return 1
|
||||||
|
fun m(this)
|
||||||
|
return 2
|
||||||
|
fun s(this)
|
||||||
|
return 4
|
||||||
|
fun u(this)
|
||||||
|
return 8
|
||||||
|
fun x(this)
|
||||||
|
return 16
|
||||||
|
|
||||||
|
fun test(this, pattern, text, flags)
|
||||||
|
if flags == nil
|
||||||
|
flags = this.u()
|
||||||
|
return pcre2_test(to_string(pattern), to_string(text), flags)
|
||||||
|
|
||||||
|
fun match(this, pattern, text, flags)
|
||||||
|
if flags == nil
|
||||||
|
flags = this.u()
|
||||||
|
return pcre2_match(to_string(pattern), to_string(text), flags)
|
||||||
|
|
||||||
|
fun find_all(this, pattern, text, flags)
|
||||||
|
if flags == nil
|
||||||
|
flags = this.u()
|
||||||
|
return pcre2_findall(to_string(pattern), to_string(text), flags)
|
||||||
|
|
@ -151,6 +151,9 @@ static const char *opcode_name(OpCode op) {
|
||||||
case OP_PCSC_CONNECT: return "PCSC_CONNECT";
|
case OP_PCSC_CONNECT: return "PCSC_CONNECT";
|
||||||
case OP_PCSC_DISCONNECT: return "PCSC_DISCONNECT";
|
case OP_PCSC_DISCONNECT: return "PCSC_DISCONNECT";
|
||||||
case OP_PCSC_TRANSMIT: return "PCSC_TRANSMIT";
|
case OP_PCSC_TRANSMIT: return "PCSC_TRANSMIT";
|
||||||
|
case OP_PCRE2_TEST: return "PCRE2_TEST";
|
||||||
|
case OP_PCRE2_MATCH: return "PCRE2_MATCH";
|
||||||
|
case OP_PCRE2_FINDALL: return "PCRE2_FINDALL";
|
||||||
default: return "???";
|
default: return "???";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,11 @@ typedef enum {
|
||||||
OP_PCSC_DISCONNECT, // pops handle id; returns 1/0
|
OP_PCSC_DISCONNECT, // pops handle id; returns 1/0
|
||||||
OP_PCSC_TRANSMIT, // pops apdu array, handle id; returns map {"data":[],"sw1":n,"sw2":n,"code":n}
|
OP_PCSC_TRANSMIT, // pops apdu array, handle id; returns map {"data":[],"sw1":n,"sw2":n,"code":n}
|
||||||
|
|
||||||
|
// PCRE2 regex ops (optional)
|
||||||
|
OP_PCRE2_TEST, // pops flags, text, pattern; pushes 1/0
|
||||||
|
OP_PCRE2_MATCH, // pops flags, text, pattern; pushes match map or Nil
|
||||||
|
OP_PCRE2_FINDALL, // pops flags, text, pattern; pushes array of match maps
|
||||||
|
|
||||||
// Sockets (UNIX platforms)
|
// Sockets (UNIX platforms)
|
||||||
OP_SOCK_TCP_LISTEN, // pops backlog, port; returns listen fd (>0) or 0
|
OP_SOCK_TCP_LISTEN, // pops backlog, port; returns listen fd (>0) or 0
|
||||||
OP_SOCK_TCP_ACCEPT, // pops listen fd; returns client fd (>0) or 0
|
OP_SOCK_TCP_ACCEPT, // pops listen fd; returns client fd (>0) or 0
|
||||||
|
|
|
||||||
38
src/parser.c
38
src/parser.c
|
|
@ -787,6 +787,44 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
|
||||||
free(name);
|
free(name);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
/* PCRE2 builtins */
|
||||||
|
if (strcmp(name, "pcre2_test") == 0) {
|
||||||
|
(*pos)++; /* '(' */
|
||||||
|
/* (pattern, text, flags) */
|
||||||
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_test expects (pattern, text, flags)" ); free(name); return 0; }
|
||||||
|
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "pcre2_test expects (pattern, text, flags)"); free(name); return 0; }
|
||||||
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_test expects (pattern, text, flags)" ); free(name); return 0; }
|
||||||
|
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "pcre2_test expects (pattern, text, flags)"); free(name); return 0; }
|
||||||
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_test expects (pattern, text, flags)" ); free(name); return 0; }
|
||||||
|
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after pcre2_test args"); free(name); return 0; }
|
||||||
|
bytecode_add_instruction(bc, OP_PCRE2_TEST, 0);
|
||||||
|
free(name);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (strcmp(name, "pcre2_match") == 0) {
|
||||||
|
(*pos)++; /* '(' */
|
||||||
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_match expects (pattern, text, flags)" ); free(name); return 0; }
|
||||||
|
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "pcre2_match expects (pattern, text, flags)"); free(name); return 0; }
|
||||||
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_match expects (pattern, text, flags)" ); free(name); return 0; }
|
||||||
|
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "pcre2_match expects (pattern, text, flags)"); free(name); return 0; }
|
||||||
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_match expects (pattern, text, flags)" ); free(name); return 0; }
|
||||||
|
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after pcre2_match args"); free(name); return 0; }
|
||||||
|
bytecode_add_instruction(bc, OP_PCRE2_MATCH, 0);
|
||||||
|
free(name);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (strcmp(name, "pcre2_findall") == 0) {
|
||||||
|
(*pos)++; /* '(' */
|
||||||
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)" ); free(name); return 0; }
|
||||||
|
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)"); free(name); return 0; }
|
||||||
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)" ); free(name); return 0; }
|
||||||
|
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)"); free(name); return 0; }
|
||||||
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)" ); free(name); return 0; }
|
||||||
|
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after pcre2_findall args"); free(name); return 0; }
|
||||||
|
bytecode_add_instruction(bc, OP_PCRE2_FINDALL, 0);
|
||||||
|
free(name);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
if (strcmp(name, "pcsc_release") == 0) {
|
if (strcmp(name, "pcsc_release") == 0) {
|
||||||
(*pos)++; /* '(' */
|
(*pos)++; /* '(' */
|
||||||
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcsc_release expects 1 argument (ctx)"); free(name); return 0; }
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcsc_release expects 1 argument (ctx)"); free(name); return 0; }
|
||||||
|
|
|
||||||
17
src/vm.c
17
src/vm.c
|
|
@ -20,6 +20,18 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
/* Ensure PCRE2 is configured consistently across the whole translation unit.
|
||||||
|
* vm.c includes many opcode implementation .c files; some use PCRE2. For PCRE2
|
||||||
|
* headers to expose the correct typedefs (e.g., pcre2_code, PCRE2_SPTR), the
|
||||||
|
* PCRE2_CODE_UNIT_WIDTH macro must be defined before the first inclusion of
|
||||||
|
* <pcre2.h>. We do this once here when PCRE2 support is enabled. */
|
||||||
|
#ifdef FUN_WITH_PCRE2
|
||||||
|
#ifndef PCRE2_CODE_UNIT_WIDTH
|
||||||
|
#define PCRE2_CODE_UNIT_WIDTH 8
|
||||||
|
#endif
|
||||||
|
#include <pcre2.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
/* forward declarations for include mapping used in error reporting */
|
/* forward declarations for include mapping used in error reporting */
|
||||||
extern char *preprocess_includes(const char *src);
|
extern char *preprocess_includes(const char *src);
|
||||||
static int map_expanded_line_to_include(const char *path, int line, char *out_path, size_t out_path_cap, int *out_line);
|
static int map_expanded_line_to_include(const char *path, int line, char *out_path, size_t out_path_cap, int *out_line);
|
||||||
|
|
@ -667,6 +679,11 @@ void vm_run(VM *vm, Bytecode *entry) {
|
||||||
#include "vm/json/from_file.c"
|
#include "vm/json/from_file.c"
|
||||||
#include "vm/json/to_file.c"
|
#include "vm/json/to_file.c"
|
||||||
|
|
||||||
|
/* PCRE2 ops */
|
||||||
|
#include "vm/pcre2/test.c"
|
||||||
|
#include "vm/pcre2/match.c"
|
||||||
|
#include "vm/pcre2/findall.c"
|
||||||
|
|
||||||
#include "vm/strings/find.c"
|
#include "vm/strings/find.c"
|
||||||
#include "vm/strings/regex_match.c"
|
#include "vm/strings/regex_match.c"
|
||||||
#include "vm/strings/regex_search.c"
|
#include "vm/strings/regex_search.c"
|
||||||
|
|
|
||||||
1
src/vm.h
1
src/vm.h
|
|
@ -40,6 +40,7 @@ static const char *opcode_names[] = {
|
||||||
"BAND","BOR","BXOR","BNOT","SHL","SHR","ROTL","ROTR",
|
"BAND","BOR","BXOR","BNOT","SHL","SHR","ROTL","ROTR",
|
||||||
"JSON_PARSE","JSON_STRINGIFY","JSON_FROM_FILE","JSON_TO_FILE",
|
"JSON_PARSE","JSON_STRINGIFY","JSON_FROM_FILE","JSON_TO_FILE",
|
||||||
"PCSC_ESTABLISH","PCSC_RELEASE","PCSC_LIST_READERS","PCSC_CONNECT","PCSC_DISCONNECT","PCSC_TRANSMIT",
|
"PCSC_ESTABLISH","PCSC_RELEASE","PCSC_LIST_READERS","PCSC_CONNECT","PCSC_DISCONNECT","PCSC_TRANSMIT",
|
||||||
|
"PCRE2_TEST","PCRE2_MATCH","PCRE2_FINDALL",
|
||||||
"SOCK_TCP_LISTEN","SOCK_TCP_ACCEPT","SOCK_TCP_CONNECT","SOCK_SEND","SOCK_RECV","SOCK_CLOSE","SOCK_UNIX_LISTEN","SOCK_UNIX_CONNECT",
|
"SOCK_TCP_LISTEN","SOCK_TCP_ACCEPT","SOCK_TCP_CONNECT","SOCK_SEND","SOCK_RECV","SOCK_CLOSE","SOCK_UNIX_LISTEN","SOCK_UNIX_CONNECT",
|
||||||
"EXIT"
|
"EXIT"
|
||||||
};
|
};
|
||||||
|
|
|
||||||
100
src/vm/pcre2/findall.c
Normal file
100
src/vm/pcre2/findall.c
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
/**
|
||||||
|
* This file is part of the Fun programming language.
|
||||||
|
* https://fun-lang.xyz/
|
||||||
|
*
|
||||||
|
* 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-11-25
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* PCRE2_FINDALL */
|
||||||
|
case OP_PCRE2_FINDALL: {
|
||||||
|
#ifdef FUN_WITH_PCRE2
|
||||||
|
Value vflags = pop_value(vm);
|
||||||
|
Value vtext = pop_value(vm);
|
||||||
|
Value vpat = pop_value(vm);
|
||||||
|
int flags = 0;
|
||||||
|
if (vflags.type == VAL_INT || vflags.type == VAL_BOOL) flags = (int)vflags.i;
|
||||||
|
char *pattern = value_to_string_alloc(&vpat);
|
||||||
|
char *subject = value_to_string_alloc(&vtext);
|
||||||
|
free_value(vflags);
|
||||||
|
free_value(vtext);
|
||||||
|
free_value(vpat);
|
||||||
|
if (!pattern || !subject) {
|
||||||
|
if (pattern) free(pattern);
|
||||||
|
if (subject) free(subject);
|
||||||
|
push_value(vm, make_array_from_values(NULL, 0));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#ifndef PCRE2_CODE_UNIT_WIDTH
|
||||||
|
#define PCRE2_CODE_UNIT_WIDTH 8
|
||||||
|
#endif
|
||||||
|
#include <pcre2.h>
|
||||||
|
int errorcode; PCRE2_SIZE erroff;
|
||||||
|
uint32_t opt = 0;
|
||||||
|
if (flags & 1) opt |= PCRE2_CASELESS; /* I */
|
||||||
|
if (flags & 2) opt |= PCRE2_MULTILINE; /* M */
|
||||||
|
if (flags & 4) opt |= PCRE2_DOTALL; /* S */
|
||||||
|
if (flags & 8) opt |= PCRE2_UTF; /* U */
|
||||||
|
if (flags & 16) opt |= PCRE2_EXTENDED; /* X */
|
||||||
|
pcre2_code *re = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, opt, &errorcode, &erroff, NULL);
|
||||||
|
if (!re) {
|
||||||
|
free(pattern); free(subject);
|
||||||
|
push_value(vm, make_array_from_values(NULL, 0));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pcre2_match_data *mdata = pcre2_match_data_create_from_pattern(re, NULL);
|
||||||
|
Value out = make_array_from_values(NULL, 0);
|
||||||
|
size_t subj_len = strlen(subject);
|
||||||
|
size_t start_off = 0;
|
||||||
|
int gcount = 0;
|
||||||
|
while (1) {
|
||||||
|
int rc = pcre2_match(re, (PCRE2_SPTR)subject, (PCRE2_SIZE)subj_len, start_off, 0, mdata, NULL);
|
||||||
|
if (rc <= 0) break;
|
||||||
|
PCRE2_SIZE *ov = pcre2_get_ovector_pointer(mdata);
|
||||||
|
int s0 = (int)ov[0];
|
||||||
|
int e0 = (int)ov[1];
|
||||||
|
/* result map for this match */
|
||||||
|
Value res = make_map_empty();
|
||||||
|
char *full = string_substr(subject, s0, e0 - s0);
|
||||||
|
(void)map_set(&res, "full", make_string(full ? full : ""));
|
||||||
|
if (full) free(full);
|
||||||
|
(void)map_set(&res, "start", make_int(s0));
|
||||||
|
(void)map_set(&res, "end", make_int(e0));
|
||||||
|
Value groups = make_array_from_values(NULL, 0);
|
||||||
|
for (int i = 1; i < rc; ++i) {
|
||||||
|
int s = (int)ov[2*i];
|
||||||
|
int e = (int)ov[2*i+1];
|
||||||
|
char *gstr = (s >= 0 && e >= s) ? string_substr(subject, s, e - s) : NULL;
|
||||||
|
Value gv = make_string(gstr ? gstr : "");
|
||||||
|
if (gstr) free(gstr);
|
||||||
|
(void)array_push(&groups, gv);
|
||||||
|
}
|
||||||
|
(void)map_set(&res, "groups", groups);
|
||||||
|
(void)array_push(&out, res);
|
||||||
|
/* advance start offset; guard against empty match */
|
||||||
|
if (e0 == s0) {
|
||||||
|
if ((size_t)e0 < subj_len) {
|
||||||
|
start_off = e0 + 1;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
start_off = e0;
|
||||||
|
}
|
||||||
|
gcount = rc;
|
||||||
|
}
|
||||||
|
pcre2_match_data_free(mdata);
|
||||||
|
pcre2_code_free(re);
|
||||||
|
free(pattern); free(subject);
|
||||||
|
push_value(vm, out);
|
||||||
|
#else
|
||||||
|
Value a = pop_value(vm); free_value(a);
|
||||||
|
Value b = pop_value(vm); free_value(b);
|
||||||
|
Value c = pop_value(vm); free_value(c);
|
||||||
|
push_value(vm, make_array_from_values(NULL, 0));
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
89
src/vm/pcre2/match.c
Normal file
89
src/vm/pcre2/match.c
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
/**
|
||||||
|
* This file is part of the Fun programming language.
|
||||||
|
* https://fun-lang.xyz/
|
||||||
|
*
|
||||||
|
* 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-11-25
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* PCRE2_MATCH */
|
||||||
|
case OP_PCRE2_MATCH: {
|
||||||
|
#ifdef FUN_WITH_PCRE2
|
||||||
|
Value vflags = pop_value(vm);
|
||||||
|
Value vtext = pop_value(vm);
|
||||||
|
Value vpat = pop_value(vm);
|
||||||
|
int flags = 0;
|
||||||
|
if (vflags.type == VAL_INT || vflags.type == VAL_BOOL) flags = (int)vflags.i;
|
||||||
|
char *pattern = value_to_string_alloc(&vpat);
|
||||||
|
char *subject = value_to_string_alloc(&vtext);
|
||||||
|
free_value(vflags);
|
||||||
|
free_value(vtext);
|
||||||
|
free_value(vpat);
|
||||||
|
if (!pattern || !subject) {
|
||||||
|
if (pattern) free(pattern);
|
||||||
|
if (subject) free(subject);
|
||||||
|
push_value(vm, make_nil());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#ifndef PCRE2_CODE_UNIT_WIDTH
|
||||||
|
#define PCRE2_CODE_UNIT_WIDTH 8
|
||||||
|
#endif
|
||||||
|
#include <pcre2.h>
|
||||||
|
int errorcode; PCRE2_SIZE erroff;
|
||||||
|
uint32_t opt = 0;
|
||||||
|
if (flags & 1) opt |= PCRE2_CASELESS; /* I */
|
||||||
|
if (flags & 2) opt |= PCRE2_MULTILINE; /* M */
|
||||||
|
if (flags & 4) opt |= PCRE2_DOTALL; /* S */
|
||||||
|
if (flags & 8) opt |= PCRE2_UTF; /* U */
|
||||||
|
if (flags & 16) opt |= PCRE2_EXTENDED; /* X */
|
||||||
|
pcre2_code *re = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, opt, &errorcode, &erroff, NULL);
|
||||||
|
if (!re) {
|
||||||
|
free(pattern); free(subject);
|
||||||
|
push_value(vm, make_nil());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pcre2_match_data *mdata = pcre2_match_data_create_from_pattern(re, NULL);
|
||||||
|
int rc = pcre2_match(re, (PCRE2_SPTR)subject, (PCRE2_SIZE)strlen(subject), 0, 0, mdata, NULL);
|
||||||
|
if (rc <= 0) {
|
||||||
|
pcre2_match_data_free(mdata);
|
||||||
|
pcre2_code_free(re);
|
||||||
|
free(pattern); free(subject);
|
||||||
|
push_value(vm, make_nil());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
PCRE2_SIZE *ov = pcre2_get_ovector_pointer(mdata);
|
||||||
|
/* Build result map */
|
||||||
|
Value res = make_map_empty();
|
||||||
|
int start0 = (int)ov[0];
|
||||||
|
int end0 = (int)ov[1];
|
||||||
|
char *full = string_substr(subject, start0, end0 - start0);
|
||||||
|
(void)map_set(&res, "full", make_string(full ? full : ""));
|
||||||
|
if (full) free(full);
|
||||||
|
(void)map_set(&res, "start", make_int(start0));
|
||||||
|
(void)map_set(&res, "end", make_int(end0));
|
||||||
|
/* groups array (excluding group 0) */
|
||||||
|
Value groups = make_array_from_values(NULL, 0);
|
||||||
|
for (int i = 1; i < rc; ++i) {
|
||||||
|
int s = (int)ov[2*i];
|
||||||
|
int e = (int)ov[2*i+1];
|
||||||
|
char *gstr = (s >= 0 && e >= s) ? string_substr(subject, s, e - s) : NULL;
|
||||||
|
Value gv = make_string(gstr ? gstr : "");
|
||||||
|
if (gstr) free(gstr);
|
||||||
|
(void)array_push(&groups, gv);
|
||||||
|
}
|
||||||
|
(void)map_set(&res, "groups", groups);
|
||||||
|
pcre2_match_data_free(mdata);
|
||||||
|
pcre2_code_free(re);
|
||||||
|
free(pattern); free(subject);
|
||||||
|
push_value(vm, res);
|
||||||
|
#else
|
||||||
|
Value a = pop_value(vm); free_value(a);
|
||||||
|
Value b = pop_value(vm); free_value(b);
|
||||||
|
Value c = pop_value(vm); free_value(c);
|
||||||
|
push_value(vm, make_nil());
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
62
src/vm/pcre2/test.c
Normal file
62
src/vm/pcre2/test.c
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
/**
|
||||||
|
* This file is part of the Fun programming language.
|
||||||
|
* https://fun-lang.xyz/
|
||||||
|
*
|
||||||
|
* 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-11-25
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* PCRE2_TEST */
|
||||||
|
case OP_PCRE2_TEST: {
|
||||||
|
#ifdef FUN_WITH_PCRE2
|
||||||
|
Value vflags = pop_value(vm);
|
||||||
|
Value vtext = pop_value(vm);
|
||||||
|
Value vpat = pop_value(vm);
|
||||||
|
int flags = 0;
|
||||||
|
if (vflags.type == VAL_INT || vflags.type == VAL_BOOL) flags = (int)vflags.i;
|
||||||
|
char *pattern = value_to_string_alloc(&vpat);
|
||||||
|
char *subject = value_to_string_alloc(&vtext);
|
||||||
|
free_value(vflags);
|
||||||
|
free_value(vtext);
|
||||||
|
free_value(vpat);
|
||||||
|
if (!pattern || !subject) {
|
||||||
|
if (pattern) free(pattern);
|
||||||
|
if (subject) free(subject);
|
||||||
|
push_value(vm, make_int(0));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#ifndef PCRE2_CODE_UNIT_WIDTH
|
||||||
|
#define PCRE2_CODE_UNIT_WIDTH 8
|
||||||
|
#endif
|
||||||
|
#include <pcre2.h>
|
||||||
|
int errorcode; PCRE2_SIZE erroff;
|
||||||
|
uint32_t opt = 0;
|
||||||
|
if (flags & 1) opt |= PCRE2_CASELESS; /* I */
|
||||||
|
if (flags & 2) opt |= PCRE2_MULTILINE; /* M */
|
||||||
|
if (flags & 4) opt |= PCRE2_DOTALL; /* S */
|
||||||
|
if (flags & 8) opt |= PCRE2_UTF; /* U */
|
||||||
|
if (flags & 16) opt |= PCRE2_EXTENDED; /* X */
|
||||||
|
pcre2_code *re = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, opt, &errorcode, &erroff, NULL);
|
||||||
|
if (!re) {
|
||||||
|
free(pattern); free(subject);
|
||||||
|
push_value(vm, make_int(0));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pcre2_match_data *mdata = pcre2_match_data_create_from_pattern(re, NULL);
|
||||||
|
int rc = pcre2_match(re, (PCRE2_SPTR)subject, (PCRE2_SIZE)strlen(subject), 0, 0, mdata, NULL);
|
||||||
|
pcre2_match_data_free(mdata);
|
||||||
|
pcre2_code_free(re);
|
||||||
|
free(pattern); free(subject);
|
||||||
|
push_value(vm, make_int(rc >= 0 ? 1 : 0));
|
||||||
|
#else
|
||||||
|
/* pop args and return 0 when PCRE2 disabled */
|
||||||
|
Value a = pop_value(vm); free_value(a);
|
||||||
|
Value b = pop_value(vm); free_value(b);
|
||||||
|
Value c = pop_value(vm); free_value(c);
|
||||||
|
push_value(vm, make_int(0));
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue