From a7bf61273eb2e525c6ae3444b980ae7fe394cae1 Mon Sep 17 00:00:00 2001 From: Lexy Plateau Date: Wed, 26 Aug 2026 18:02:39 +0200 Subject: [PATCH 1/3] refactor: rename hash module to ark_hash to avoid conflict with abseil hash --- .clang-format-ignore | 16 +++++++++------- .gitignore | 5 +---- src/CMakeLists.txt | 2 +- src/{hash => ark_hash}/CMakeLists.txt | 4 ++-- src/{hash => ark_hash}/documentation/README.md | 0 src/{hash => ark_hash}/include/md5.hpp | 0 src/{hash => ark_hash}/src/main.cpp | 0 src/{hash => ark_hash}/src/md5.cpp | 0 src/{hash => ark_hash}/tests/main.ark | 0 src/{hash => ark_hash}/tests/run | 0 10 files changed, 13 insertions(+), 14 deletions(-) rename src/{hash => ark_hash}/CMakeLists.txt (86%) rename src/{hash => ark_hash}/documentation/README.md (100%) rename src/{hash => ark_hash}/include/md5.hpp (100%) rename src/{hash => ark_hash}/src/main.cpp (100%) rename src/{hash => ark_hash}/src/md5.cpp (100%) rename src/{hash => ark_hash}/tests/main.ark (100%) rename src/{hash => ark_hash}/tests/run (100%) diff --git a/.clang-format-ignore b/.clang-format-ignore index 9723833..711e99e 100644 --- a/.clang-format-ignore +++ b/.clang-format-ignore @@ -1,7 +1,9 @@ -bitwise/documentation -console/documentation -hash/documentation -http/documentation -http/bulk_req.py -json/documentation -random/documentation +src/bitwise/documentation +src/console/documentation +src/hash/documentation +src/http/documentation +src/http/bulk_req.py + +draft/database/documentation +draft/json/documentation +draft/re/documentation diff --git a/.gitignore b/.gitignore index b4a272b..4865160 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,10 @@ build/ +cmake-build-*/ __pycache__/ __arkscript__/ -cformat.ps1 .idea/ -cmake-build-*/ -build/ *.o *.out *.dll @@ -15,4 +13,3 @@ build/ *.so draft/playground - diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 442be8b..96923a4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,4 @@ add_subdirectory(bitwise) add_subdirectory(console) -add_subdirectory(hash) +add_subdirectory(ark_hash) add_subdirectory(http) diff --git a/src/hash/CMakeLists.txt b/src/ark_hash/CMakeLists.txt similarity index 86% rename from src/hash/CMakeLists.txt rename to src/ark_hash/CMakeLists.txt index aa17173..f25c2b7 100644 --- a/src/hash/CMakeLists.txt +++ b/src/ark_hash/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.16) -project(hash) +project(ark_hash) set(CMAKE_POSITION_INDEPENDENT_CODE ON) @@ -21,4 +21,4 @@ target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy - "$" ${ark_SOURCE_DIR}/lib/${PROJECT_NAME}.arkm) + "$" ${ark_SOURCE_DIR}/lib/hash.arkm) diff --git a/src/hash/documentation/README.md b/src/ark_hash/documentation/README.md similarity index 100% rename from src/hash/documentation/README.md rename to src/ark_hash/documentation/README.md diff --git a/src/hash/include/md5.hpp b/src/ark_hash/include/md5.hpp similarity index 100% rename from src/hash/include/md5.hpp rename to src/ark_hash/include/md5.hpp diff --git a/src/hash/src/main.cpp b/src/ark_hash/src/main.cpp similarity index 100% rename from src/hash/src/main.cpp rename to src/ark_hash/src/main.cpp diff --git a/src/hash/src/md5.cpp b/src/ark_hash/src/md5.cpp similarity index 100% rename from src/hash/src/md5.cpp rename to src/ark_hash/src/md5.cpp diff --git a/src/hash/tests/main.ark b/src/ark_hash/tests/main.ark similarity index 100% rename from src/hash/tests/main.ark rename to src/ark_hash/tests/main.ark diff --git a/src/hash/tests/run b/src/ark_hash/tests/run similarity index 100% rename from src/hash/tests/run rename to src/ark_hash/tests/run From 5e7180bca8e3764dd846a497e8c0f02980ad966b Mon Sep 17 00:00:00 2001 From: Lexy Plateau Date: Wed, 26 Aug 2026 18:06:24 +0200 Subject: [PATCH 2/3] feat: ARK-316 adding new module re --- .gitmodules | 6 ++ draft/CMakeLists.txt | 1 + draft/re/CMakeLists.txt | 27 +++++ draft/re/README.md | 0 draft/re/documentation/README.md | 0 draft/re/include/module.hpp | 19 ++++ draft/re/include/utils.hpp | 63 ++++++++++++ draft/re/src/impl.cpp | 170 +++++++++++++++++++++++++++++++ draft/re/src/main.cpp | 17 ++++ draft/re/src/utils.cpp | 82 +++++++++++++++ draft/re/tests/main.ark | 0 draft/re/tests/run | 6 ++ submodules/abseil | 1 + submodules/re2 | 1 + 14 files changed, 393 insertions(+) create mode 100644 draft/re/CMakeLists.txt create mode 100644 draft/re/README.md create mode 100644 draft/re/documentation/README.md create mode 100644 draft/re/include/module.hpp create mode 100644 draft/re/include/utils.hpp create mode 100644 draft/re/src/impl.cpp create mode 100644 draft/re/src/main.cpp create mode 100644 draft/re/src/utils.cpp create mode 100644 draft/re/tests/main.ark create mode 100644 draft/re/tests/run create mode 160000 submodules/abseil create mode 160000 submodules/re2 diff --git a/.gitmodules b/.gitmodules index 9563436..f45cb26 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,9 @@ [submodule "submodules/termcolor"] path = submodules/termcolor url = https://github.com/ikalnytskyi/termcolor +[submodule "submodules/abseil"] + path = submodules/abseil + url = https://github.com/abseil/abseil-cpp.git +[submodule "submodules/re2"] + path = submodules/re2 + url = https://github.com/google/re2.git diff --git a/draft/CMakeLists.txt b/draft/CMakeLists.txt index 21eb1c0..02ddb2d 100644 --- a/draft/CMakeLists.txt +++ b/draft/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(database) add_subdirectory(json) +add_subdirectory(re) diff --git a/draft/re/CMakeLists.txt b/draft/re/CMakeLists.txt new file mode 100644 index 0000000..1d8412c --- /dev/null +++ b/draft/re/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.16) + +project(re) + +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +set(ABSL_ENABLE_INSTALL ON) +set(ABSL_PROPAGATE_CXX_STD ON) +add_subdirectory(${Modules_SOURCE_DIR}/submodules/abseil abseil EXCLUDE_FROM_ALL SYSTEM) +add_subdirectory(${Modules_SOURCE_DIR}/submodules/re2 re2 EXCLUDE_FROM_ALL SYSTEM) + +include_directories(${PROJECT_SOURCE_DIR}/include) +file(GLOB_RECURSE SOURCE_FILES + ${PROJECT_SOURCE_DIR}/src/*.cpp + ${ark_SOURCE_DIR}/thirdparties/fmt/src/format.cc) + +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES}) + +target_include_directories(${PROJECT_NAME} SYSTEM PRIVATE ${ark_SOURCE_DIR}/include) +target_link_libraries(${PROJECT_NAME} PRIVATE ArkReactor re2::re2) + +target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) + +add_custom_command(TARGET ${PROJECT_NAME} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + "$" ${ark_SOURCE_DIR}/lib/${PROJECT_NAME}.arkm) diff --git a/draft/re/README.md b/draft/re/README.md new file mode 100644 index 0000000..e69de29 diff --git a/draft/re/documentation/README.md b/draft/re/documentation/README.md new file mode 100644 index 0000000..e69de29 diff --git a/draft/re/include/module.hpp b/draft/re/include/module.hpp new file mode 100644 index 0000000..85f482e --- /dev/null +++ b/draft/re/include/module.hpp @@ -0,0 +1,19 @@ +#ifndef RE_REGEX_HPP +#define RE_REGEX_HPP + +#include + +namespace Regex +{ + using namespace Ark; + + Value search(std::vector& args, VM* vm); + Value match(std::vector& args, VM* vm); + Value full_match(std::vector& args, VM* vm); + Value find_all(std::vector& args, VM* vm); + Value sub(std::vector& args, VM* vm); + Value split(std::vector& args, VM* vm); + Value escape(std::vector& args, VM* vm); +} + +#endif // RE_REGEX_HPP diff --git a/draft/re/include/utils.hpp b/draft/re/include/utils.hpp new file mode 100644 index 0000000..97957b0 --- /dev/null +++ b/draft/re/include/utils.hpp @@ -0,0 +1,63 @@ +#ifndef RE_UTILS_HPP +#define RE_UTILS_HPP + +#include +#include + +#include +#include + +namespace Regex +{ + struct Span + { + long start; + std::size_t size; + bool empty; + }; + + struct Match + { + std::vector spans; + + [[nodiscard]] inline long start() const + { + return spans.front().start; + } + + [[nodiscard]] inline std::size_t end() const + { + return spans.front().start + spans.front().size; + } + + [[nodiscard]] inline std::size_t size() const + { + return spans.front().size; + } + }; + + struct MatchImpl_t + { + std::string text; + std::vector matches; + bool empty; + bool expect_many; + }; + + inline Span asSpan(const absl::string_view& sv, const std::string& text) + { + return Span { .start = static_cast(sv.data() - text.data()), .size = sv.size(), .empty = false }; + } + + inline Span getFirstSpan(const std::vector& groups, const std::string& text) + { + if (const auto& it = groups.front(); it.data() == nullptr) + return Span { .start = 0, .size = 0, .empty = true }; + else + return asSpan(it, text); + } + + MatchImpl_t matchImpl(RE2::Anchor anchor, const std::string& regex, const std::string& text, bool stop_at_first); +} + +#endif // RE_UTILS_HPP diff --git a/draft/re/src/impl.cpp b/draft/re/src/impl.cpp new file mode 100644 index 0000000..2944e27 --- /dev/null +++ b/draft/re/src/impl.cpp @@ -0,0 +1,170 @@ +#include +#include + +#include + +#include + +namespace Regex +{ + using namespace Ark; + using namespace Ark::literals; + using namespace re2; + + Value toValue(const Match& match, const std::string& text) + { + if (const auto& span = match.spans.front(); !span.empty) + return Value(text.substr(span.start, span.size)); + return Nil; + } + + Value toValue(const MatchImpl_t& res) + { + if (res.empty) + return Nil; + + if (res.expect_many) + { + Value output(ValueType::List); + for (const Match& match : res.matches) + { + // todo: create a dict with all the groups + output.push_back(toValue(match, res.text)); + } + return output; + } + + const Match& match = res.matches.front(); + return toValue(match, res.text); + } + + Value search(std::vector& args, VM* vm) + { + if (!types::check(args, ValueType::String, ValueType::String)) + throw types::TypeCheckingError( + "re:search", + { { types::Contract { { types::Typedef("regex", ValueType::String), types::Typedef("input", ValueType::String) } } } }, + args); + const std::string& regex = args[0].string(); + const std::string& input = args[1].string(); + return toValue(matchImpl(RE2::UNANCHORED, regex, input, true)); + } + + Value match(std::vector& args, VM* vm) + { + if (!types::check(args, ValueType::String, ValueType::String)) + throw types::TypeCheckingError( + "re:match", + { { types::Contract { { types::Typedef("regex", ValueType::String), types::Typedef("input", ValueType::String) } } } }, + args); + const std::string& regex = args[0].string(); + const std::string& input = args[1].string(); + return toValue(matchImpl(RE2::ANCHOR_START, regex, input, true)); + } + + Value full_match(std::vector& args, VM* vm) + { + if (!types::check(args, ValueType::String, ValueType::String)) + throw types::TypeCheckingError( + "re:fullMatch", + { { types::Contract { { types::Typedef("regex", ValueType::String), types::Typedef("input", ValueType::String) } } } }, + args); + const std::string& regex = args[0].string(); + const std::string& input = args[1].string(); + return toValue(matchImpl(RE2::ANCHOR_BOTH, regex, input, true)); + } + + Value find_all(std::vector& args, VM* vm) + { + if (!types::check(args, ValueType::String, ValueType::String)) + throw types::TypeCheckingError( + "re:findAll", + { { types::Contract { { types::Typedef("regex", ValueType::String), types::Typedef("input", ValueType::String) } } } }, + args); + const std::string& regex = args[0].string(); + const std::string& input = args[1].string(); + return toValue(matchImpl(RE2::UNANCHORED, regex, input, false)); + } + + Value sub(std::vector& args, VM* vm) + { + if (!types::check(args, ValueType::String, ValueType::String, ValueType::String)) + throw types::TypeCheckingError( + "re:sub", + { { types::Contract { { types::Typedef("regex", ValueType::String), + types::Typedef("replacement", ValueType::String), + types::Typedef("input", ValueType::String) } } } }, + args); + const std::string& regex = args[0].string(); + const std::string& replacement = args[1].string(); + std::string input = args[2].string(); + + // todo: control how many times we want to replace? + RE2::GlobalReplace(&input, regex, replacement); + return Value(input); + } + + Value split(std::vector& args, VM* vm) + { + if (!types::check(args, ValueType::String, ValueType::String) && !types::check(args, ValueType::String, ValueType::String, ValueType::Number)) + throw types::TypeCheckingError( + "re:split", + { { types::Contract { + { types::Typedef("regex", ValueType::String), + types::Typedef("input", ValueType::String) } }, + types::Contract { { types::Typedef("regex", ValueType::String), + types::Typedef("input", ValueType::String), + types::Typedef("max_split", ValueType::Number) } } } }, + args); + const std::string& regex = args[0].string(); + const std::string& input = args[1].string(); + long max_split = 0; + if (args.size() == 3) + max_split = static_cast(args[2].number()); + + Value output(ValueType::List); + if (max_split < 0) + { + output.push_back(args[1]); + return output; + } + + long num_split = 0; + std::size_t end = 0; + const MatchImpl_t res = matchImpl(RE2::UNANCHORED, regex, input, false); + if (res.empty) + return Nil; + + for (const Match& match : res.matches) + { + output.push_back(Value(res.text.substr(end, match.start() - end))); + for (std::size_t i = 1; i < match.spans.size(); ++i) + { + if (!match.spans[i].empty) + output.push_back(Value(res.text.substr(match.spans[i].start, match.spans[i].size))); + else + output.push_back(Nil); + } + + end = match.end(); + num_split++; + + if (num_split == max_split) + break; + } + if (end < res.text.size()) + output.push_back(Value(res.text.substr(end))); + + return output; + } + + Value escape(std::vector& args, VM* vm) + { + if (!types::check(args, ValueType::String)) + throw types::TypeCheckingError( + "re:escape", + { { types::Contract { { types::Typedef("text", ValueType::String) } } } }, + args); + return Value(RE2::QuoteMeta(args[0].string())); + } +} diff --git a/draft/re/src/main.cpp b/draft/re/src/main.cpp new file mode 100644 index 0000000..62501e2 --- /dev/null +++ b/draft/re/src/main.cpp @@ -0,0 +1,17 @@ +#include + +ARK_API Ark::mapping* getFunctionsMapping() +{ + static Ark::mapping map[] = { + { "re:search", Regex::search }, + { "re:match", Regex::match }, + { "re:fullMatch", Regex::full_match }, + { "re:findAll", Regex::find_all }, + { "re:sub", Regex::sub }, + { "re:split", Regex::split }, + { "re:escape", Regex::escape }, + { nullptr, nullptr } + }; + + return map; +} diff --git a/draft/re/src/utils.cpp b/draft/re/src/utils.cpp new file mode 100644 index 0000000..ab6dec4 --- /dev/null +++ b/draft/re/src/utils.cpp @@ -0,0 +1,82 @@ +#include + +#include + +namespace Regex +{ + MatchImpl_t matchImpl(const RE2::Anchor anchor, const std::string& regex, const std::string& text, const bool stop_at_first) + { + constexpr std::size_t start_pos = 0; + const std::size_t end_pos = text.size(); + + auto options = RE2::Options(RE2::Quiet); + options.set_case_sensitive(true); + options.set_posix_syntax(false); + options.set_perl_classes(true); + + RE2 pattern(regex, options); + if (!pattern.ok()) + throw std::runtime_error(fmt::format("re: error while compiling pattern '{}', {}", regex, pattern.error())); + + const int num_groups = 1 + pattern.NumberOfCapturingGroups(); // +1 to have $0 as well + std::vector groups; + groups.resize(num_groups); + + if (!pattern.Match(text, start_pos, end_pos, anchor, groups.data(), num_groups)) + return { .empty = true }; + + MatchImpl_t output { + .text = text, + .empty = false, + .expect_many = !stop_at_first + }; + if (stop_at_first) + { + Match match; + for (const auto& it : groups) + { + if (it.data() == nullptr) + match.spans.emplace_back(Span { 0, 0, true }); + else + match.spans.emplace_back(asSpan(it, text)); + } + output.matches.emplace_back(match); + } + else + { + std::size_t pos = start_pos; + Span span = getFirstSpan(groups, text); + + while (true) + { + if (span.empty) + break; + + std::vector spans; + for (const auto& it : groups) + { + if (it.data() == nullptr) + spans.emplace_back(Span { 0, 0, true }); + else + spans.emplace_back(asSpan(it, text)); + } + output.matches.emplace_back(Match { .spans = spans }); + + if (pos == end_pos) + break; + if (pos == span.start + span.size) + // we matched the empty string at `pos` and would be stuck, so in order to make progress, + // increment the offset + pos++; + else + pos = span.start + span.size; + + // todo: should we reset `groups` each time? + if (!pattern.Match(text, pos, end_pos, anchor, groups.data(), num_groups)) + break; + span = getFirstSpan(groups, text); + } + } + return output; + } +} diff --git a/draft/re/tests/main.ark b/draft/re/tests/main.ark new file mode 100644 index 0000000..e69de29 diff --git a/draft/re/tests/run b/draft/re/tests/run new file mode 100644 index 0000000..7f7c4f5 --- /dev/null +++ b/draft/re/tests/run @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +ARKSCRIPT=$1 +ARKLIB=$2 + +$ARKSCRIPT --lib $ARKLIB main.ark diff --git a/submodules/abseil b/submodules/abseil new file mode 160000 index 0000000..fe690e6 --- /dev/null +++ b/submodules/abseil @@ -0,0 +1 @@ +Subproject commit fe690e639469d2a3c86bc74dd2456371288d1f28 diff --git a/submodules/re2 b/submodules/re2 new file mode 160000 index 0000000..972a15c --- /dev/null +++ b/submodules/re2 @@ -0,0 +1 @@ +Subproject commit 972a15cedd008d846f1a39b2e88ce48d7f166cbd From ff667b3790505cf8e6e20850ed5ce523c630e88c Mon Sep 17 00:00:00 2001 From: Lexy Plateau Date: Thu, 27 Aug 2026 14:11:55 +0200 Subject: [PATCH 3/3] feat(re): ARK-316 return a dictionary with details for each match --- draft/re/src/impl.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/draft/re/src/impl.cpp b/draft/re/src/impl.cpp index 2944e27..fb2e1f9 100644 --- a/draft/re/src/impl.cpp +++ b/draft/re/src/impl.cpp @@ -1,7 +1,9 @@ +#include #include #include #include +#include #include @@ -13,9 +15,21 @@ namespace Regex Value toValue(const Match& match, const std::string& text) { - if (const auto& span = match.spans.front(); !span.empty) - return Value(text.substr(span.start, span.size)); - return Nil; + internal::Dict dict; + const auto& span = match.spans.front(); + if (!span.empty) + dict.set(Value("match"), Value(text.substr(span.start, span.size))); + else + dict.set(Value("match"), Nil); + dict.set(Value("start"), Value(static_cast(span.start))); + dict.set(Value("end"), Value(static_cast(span.size + span.start))); + + Value groups(ValueType::List); + for (const Span& s : match.spans | std::ranges::views::drop(1)) + groups.push_back(Value(text.substr(s.start, s.size))); + dict.set(Value("groups"), groups); + + return Value(std::move(dict)); } Value toValue(const MatchImpl_t& res)