Skip to content

Commit 64a5cde

Browse files
committed
feat: ARK-316 adding new module re
1 parent a7bf612 commit 64a5cde

14 files changed

Lines changed: 392 additions & 0 deletions

File tree

.gitmodules

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,9 @@
77
[submodule "submodules/termcolor"]
88
path = submodules/termcolor
99
url = https://github.com/ikalnytskyi/termcolor
10+
[submodule "submodules/abseil"]
11+
path = submodules/abseil
12+
url = https://github.com/abseil/abseil-cpp.git
13+
[submodule "submodules/re2"]
14+
path = submodules/re2
15+
url = https://github.com/google/re2.git

draft/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
add_subdirectory(database)
22
add_subdirectory(json)
3+
add_subdirectory(re)

draft/re/CMakeLists.txt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
cmake_minimum_required(VERSION 3.16)
2+
3+
project(re)
4+
5+
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
6+
7+
set(ABSL_ENABLE_INSTALL ON)
8+
set(ABSL_PROPAGATE_CXX_STD ON)
9+
add_subdirectory(${Modules_SOURCE_DIR}/submodules/abseil abseil EXCLUDE_FROM_ALL SYSTEM)
10+
add_subdirectory(${Modules_SOURCE_DIR}/submodules/re2 re2 EXCLUDE_FROM_ALL SYSTEM)
11+
12+
include_directories(${PROJECT_SOURCE_DIR}/include)
13+
file(GLOB_RECURSE SOURCE_FILES
14+
${PROJECT_SOURCE_DIR}/src/*.cpp)
15+
16+
add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES})
17+
18+
target_include_directories(${PROJECT_NAME} SYSTEM PRIVATE ${ark_SOURCE_DIR}/include)
19+
target_link_libraries(${PROJECT_NAME} PRIVATE ArkReactor re2::re2)
20+
21+
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20)
22+
23+
add_custom_command(TARGET ${PROJECT_NAME}
24+
POST_BUILD
25+
COMMAND ${CMAKE_COMMAND} -E copy
26+
"$<TARGET_FILE:${PROJECT_NAME}>" ${ark_SOURCE_DIR}/lib/${PROJECT_NAME}.arkm)

draft/re/README.md

Whitespace-only changes.

draft/re/documentation/README.md

Whitespace-only changes.

draft/re/include/module.hpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#ifndef RE_REGEX_HPP
2+
#define RE_REGEX_HPP
3+
4+
#include <Ark/Module.hpp>
5+
6+
namespace Regex
7+
{
8+
using namespace Ark;
9+
10+
Value search(std::vector<Value>& args, VM* vm);
11+
Value match(std::vector<Value>& args, VM* vm);
12+
Value full_match(std::vector<Value>& args, VM* vm);
13+
Value find_all(std::vector<Value>& args, VM* vm);
14+
Value sub(std::vector<Value>& args, VM* vm);
15+
Value split(std::vector<Value>& args, VM* vm);
16+
Value escape(std::vector<Value>& args, VM* vm);
17+
}
18+
19+
#endif // RE_REGEX_HPP

draft/re/include/utils.hpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#ifndef RE_UTILS_HPP
2+
#define RE_UTILS_HPP
3+
4+
#include <vector>
5+
#include <string>
6+
7+
#include <re2/re2.h>
8+
#include <absl/strings/string_view.h>
9+
10+
namespace Regex
11+
{
12+
struct Span
13+
{
14+
long start;
15+
std::size_t size;
16+
bool empty;
17+
};
18+
19+
struct Match
20+
{
21+
std::vector<Span> spans;
22+
23+
[[nodiscard]] inline long start() const
24+
{
25+
return spans.front().start;
26+
}
27+
28+
[[nodiscard]] inline std::size_t end() const
29+
{
30+
return spans.front().start + spans.front().size;
31+
}
32+
33+
[[nodiscard]] inline std::size_t size() const
34+
{
35+
return spans.front().size;
36+
}
37+
};
38+
39+
struct MatchImpl_t
40+
{
41+
std::string text;
42+
std::vector<Match> matches;
43+
bool empty;
44+
bool expect_many;
45+
};
46+
47+
inline Span asSpan(const absl::string_view& sv, const std::string& text)
48+
{
49+
return Span { .start = static_cast<long>(sv.data() - text.data()), .size = sv.size(), .empty = false };
50+
}
51+
52+
inline Span getFirstSpan(const std::vector<absl::string_view>& groups, const std::string& text)
53+
{
54+
if (const auto& it = groups.front(); it.data() == nullptr)
55+
return Span { .start = 0, .size = 0, .empty = true };
56+
else
57+
return asSpan(it, text);
58+
}
59+
60+
MatchImpl_t matchImpl(RE2::Anchor anchor, const std::string& regex, const std::string& text, bool stop_at_first);
61+
}
62+
63+
#endif // RE_UTILS_HPP

draft/re/src/impl.cpp

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
#include <module.hpp>
2+
#include <utils.hpp>
3+
4+
#include <vector>
5+
6+
#include <re2/re2.h>
7+
8+
namespace Regex
9+
{
10+
using namespace Ark;
11+
using namespace Ark::literals;
12+
using namespace re2;
13+
14+
Value toValue(const Match& match, const std::string& text)
15+
{
16+
if (const auto& span = match.spans.front(); !span.empty)
17+
return Value(text.substr(span.start, span.size));
18+
return Nil;
19+
}
20+
21+
Value toValue(const MatchImpl_t& res)
22+
{
23+
if (res.empty)
24+
return Nil;
25+
26+
if (res.expect_many)
27+
{
28+
Value output(ValueType::List);
29+
for (const Match& match : res.matches)
30+
{
31+
// todo: create a dict with all the groups
32+
output.push_back(toValue(match, res.text));
33+
}
34+
return output;
35+
}
36+
37+
const Match& match = res.matches.front();
38+
return toValue(match, res.text);
39+
}
40+
41+
Value search(std::vector<Value>& args, VM* vm)
42+
{
43+
if (!types::check(args, ValueType::String, ValueType::String))
44+
throw types::TypeCheckingError(
45+
"re:search",
46+
{ { types::Contract { { types::Typedef("regex", ValueType::String), types::Typedef("input", ValueType::String) } } } },
47+
args);
48+
const std::string& regex = args[0].string();
49+
const std::string& input = args[1].string();
50+
return toValue(matchImpl(RE2::UNANCHORED, regex, input, true));
51+
}
52+
53+
Value match(std::vector<Value>& args, VM* vm)
54+
{
55+
if (!types::check(args, ValueType::String, ValueType::String))
56+
throw types::TypeCheckingError(
57+
"re:match",
58+
{ { types::Contract { { types::Typedef("regex", ValueType::String), types::Typedef("input", ValueType::String) } } } },
59+
args);
60+
const std::string& regex = args[0].string();
61+
const std::string& input = args[1].string();
62+
return toValue(matchImpl(RE2::ANCHOR_START, regex, input, true));
63+
}
64+
65+
Value full_match(std::vector<Value>& args, VM* vm)
66+
{
67+
if (!types::check(args, ValueType::String, ValueType::String))
68+
throw types::TypeCheckingError(
69+
"re:fullMatch",
70+
{ { types::Contract { { types::Typedef("regex", ValueType::String), types::Typedef("input", ValueType::String) } } } },
71+
args);
72+
const std::string& regex = args[0].string();
73+
const std::string& input = args[1].string();
74+
return toValue(matchImpl(RE2::ANCHOR_BOTH, regex, input, true));
75+
}
76+
77+
Value find_all(std::vector<Value>& args, VM* vm)
78+
{
79+
if (!types::check(args, ValueType::String, ValueType::String))
80+
throw types::TypeCheckingError(
81+
"re:findAll",
82+
{ { types::Contract { { types::Typedef("regex", ValueType::String), types::Typedef("input", ValueType::String) } } } },
83+
args);
84+
const std::string& regex = args[0].string();
85+
const std::string& input = args[1].string();
86+
return toValue(matchImpl(RE2::UNANCHORED, regex, input, false));
87+
}
88+
89+
Value sub(std::vector<Value>& args, VM* vm)
90+
{
91+
if (!types::check(args, ValueType::String, ValueType::String, ValueType::String))
92+
throw types::TypeCheckingError(
93+
"re:sub",
94+
{ { types::Contract { { types::Typedef("regex", ValueType::String),
95+
types::Typedef("replacement", ValueType::String),
96+
types::Typedef("input", ValueType::String) } } } },
97+
args);
98+
const std::string& regex = args[0].string();
99+
const std::string& replacement = args[1].string();
100+
std::string input = args[2].string();
101+
102+
// todo: control how many times we want to replace?
103+
RE2::GlobalReplace(&input, regex, replacement);
104+
return Value(input);
105+
}
106+
107+
Value split(std::vector<Value>& args, VM* vm)
108+
{
109+
if (!types::check(args, ValueType::String, ValueType::String) && !types::check(args, ValueType::String, ValueType::String, ValueType::Number))
110+
throw types::TypeCheckingError(
111+
"re:split",
112+
{ { types::Contract {
113+
{ types::Typedef("regex", ValueType::String),
114+
types::Typedef("input", ValueType::String) } },
115+
types::Contract { { types::Typedef("regex", ValueType::String),
116+
types::Typedef("input", ValueType::String),
117+
types::Typedef("max_split", ValueType::Number) } } } },
118+
args);
119+
const std::string& regex = args[0].string();
120+
const std::string& input = args[1].string();
121+
long max_split = 0;
122+
if (args.size() == 3)
123+
max_split = static_cast<long>(args[2].number());
124+
125+
Value output(ValueType::List);
126+
if (max_split < 0)
127+
{
128+
output.push_back(args[1]);
129+
return output;
130+
}
131+
132+
long num_split = 0;
133+
std::size_t end = 0;
134+
const MatchImpl_t res = matchImpl(RE2::UNANCHORED, regex, input, false);
135+
if (res.empty)
136+
return Nil;
137+
138+
for (const Match& match : res.matches)
139+
{
140+
output.push_back(Value(res.text.substr(end, match.start() - end)));
141+
for (std::size_t i = 1; i < match.spans.size(); ++i)
142+
{
143+
if (!match.spans[i].empty)
144+
output.push_back(Value(res.text.substr(match.spans[i].start, match.spans[i].size)));
145+
else
146+
output.push_back(Nil);
147+
}
148+
149+
end = match.end();
150+
num_split++;
151+
152+
if (num_split == max_split)
153+
break;
154+
}
155+
if (end < res.text.size())
156+
output.push_back(Value(res.text.substr(end)));
157+
158+
return output;
159+
}
160+
161+
Value escape(std::vector<Value>& args, VM* vm)
162+
{
163+
if (!types::check(args, ValueType::String))
164+
throw types::TypeCheckingError(
165+
"re:escape",
166+
{ { types::Contract { { types::Typedef("text", ValueType::String) } } } },
167+
args);
168+
return Value(RE2::QuoteMeta(args[0].string()));
169+
}
170+
}

draft/re/src/main.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#include <module.hpp>
2+
3+
ARK_API Ark::mapping* getFunctionsMapping()
4+
{
5+
static Ark::mapping map[] = {
6+
{ "re:search", Regex::search },
7+
{ "re:match", Regex::match },
8+
{ "re:fullMatch", Regex::full_match },
9+
{ "re:findAll", Regex::find_all },
10+
{ "re:sub", Regex::sub },
11+
{ "re:split", Regex::split },
12+
{ "re:escape", Regex::escape },
13+
{ nullptr, nullptr }
14+
};
15+
16+
return map;
17+
}

draft/re/src/utils.cpp

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#include <utils.hpp>
2+
3+
#include <fmt/format.h>
4+
5+
namespace Regex
6+
{
7+
MatchImpl_t matchImpl(const RE2::Anchor anchor, const std::string& regex, const std::string& text, const bool stop_at_first)
8+
{
9+
constexpr std::size_t start_pos = 0;
10+
const std::size_t end_pos = text.size();
11+
12+
auto options = RE2::Options(RE2::Quiet);
13+
options.set_case_sensitive(true);
14+
options.set_posix_syntax(false);
15+
options.set_perl_classes(true);
16+
17+
RE2 pattern(regex, options);
18+
if (!pattern.ok())
19+
throw std::runtime_error(fmt::format("re: error while compiling pattern '{}', {}", regex, pattern.error()));
20+
21+
const int num_groups = 1 + pattern.NumberOfCapturingGroups(); // +1 to have $0 as well
22+
std::vector<absl::string_view> groups;
23+
groups.resize(num_groups);
24+
25+
if (!pattern.Match(text, start_pos, end_pos, anchor, groups.data(), num_groups))
26+
return { .empty = true };
27+
28+
MatchImpl_t output {
29+
.text = text,
30+
.empty = false,
31+
.expect_many = !stop_at_first
32+
};
33+
if (stop_at_first)
34+
{
35+
Match match;
36+
for (const auto& it : groups)
37+
{
38+
if (it.data() == nullptr)
39+
match.spans.emplace_back(Span { 0, 0, true });
40+
else
41+
match.spans.emplace_back(asSpan(it, text));
42+
}
43+
output.matches.emplace_back(match);
44+
}
45+
else
46+
{
47+
std::size_t pos = start_pos;
48+
Span span = getFirstSpan(groups, text);
49+
50+
while (true)
51+
{
52+
if (span.empty)
53+
break;
54+
55+
std::vector<Span> spans;
56+
for (const auto& it : groups)
57+
{
58+
if (it.data() == nullptr)
59+
spans.emplace_back(Span { 0, 0, true });
60+
else
61+
spans.emplace_back(asSpan(it, text));
62+
}
63+
output.matches.emplace_back(Match { .spans = spans });
64+
65+
if (pos == end_pos)
66+
break;
67+
if (pos == span.start + span.size)
68+
// we matched the empty string at `pos` and would be stuck, so in order to make progress,
69+
// increment the offset
70+
pos++;
71+
else
72+
pos = span.start + span.size;
73+
74+
// todo: should we reset `groups` each time?
75+
if (!pattern.Match(text, pos, end_pos, anchor, groups.data(), num_groups))
76+
break;
77+
span = getFirstSpan(groups, text);
78+
}
79+
}
80+
return output;
81+
}
82+
}

0 commit comments

Comments
 (0)