• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ArkScript-lang / Ark / 25795566552

13 May 2026 11:14AM UTC coverage: 94.076% (-0.002%) from 94.078%
25795566552

push

github

SuperFola
fix(macro): $repr should be able to show the representation of a macro, when given one

6 of 6 new or added lines in 1 file covered. (100.0%)

8 existing lines in 2 files now uncovered.

9894 of 10517 relevant lines covered (94.08%)

719240.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.94
/src/arkreactor/Builtins/String.cpp
1
#include <Ark/Builtins/Builtins.hpp>
2

3
#include <utility>
4
#include <cmath>
5
#include <utf8.hpp>
6
#include <fmt/args.h>
7
#include <fmt/base.h>
8
#include <fmt/ostream.h>
9
#include <fmt/core.h>
10
#include <fmt/ranges.h>
11
#include <fmt/format.h>
12

13
#include <Ark/TypeChecker.hpp>
14
#include <Ark/VM/VM.hpp>
15
#include <Ark/Compiler/AST/utf8_char.hpp>
16

17
struct value_wrapper
18
{
19
    const Ark::Value& value;
20
    Ark::VM* vm_ptr;
21
    bool nested = false;
22
};
23

24
template <>
25
struct fmt::formatter<value_wrapper>
14✔
26
{
27
private:
28
    fmt::basic_string_view<char> opening_bracket_ = fmt::detail::string_literal<char, '['> {};
14✔
29
    fmt::basic_string_view<char> closing_bracket_ = fmt::detail::string_literal<char, ']'> {};
14✔
30
    fmt::basic_string_view<char> separator_ = fmt::detail::string_literal<char, ' '> {};
14✔
31
    bool is_debug = false;
14✔
32
    bool is_literal_str = false;
14✔
33
    fmt::formatter<std::string> underlying_;
34

35
public:
36
    void set_brackets(const fmt::basic_string_view<char> open, const fmt::basic_string_view<char> close)
8✔
37
    {
8✔
38
        opening_bracket_ = open;
8✔
39
        closing_bracket_ = close;
8✔
40
    }
8✔
41

42
    void set_separator(const fmt::basic_string_view<char> sep)
6✔
43
    {
6✔
44
        separator_ = sep;
6✔
45
    }
6✔
46

47
    format_parse_context::iterator parse(fmt::format_parse_context& ctx)
13✔
48
    {
13✔
49
        auto it = ctx.begin();
13✔
50
        const auto end = ctx.end();
13✔
51
        if (it == end)
13✔
52
            return underlying_.parse(ctx);
1✔
53

54
        switch (detail::to_ascii(*it))
12✔
55
        {
4✔
56
            case 'n':
57
                set_brackets({}, {});
4✔
58
                ++it;
4✔
59
                if (it == end)
4✔
UNCOV
60
                    report_error("invalid format specifier");
×
61
                if (*it == '}')
4✔
62
                    return it;
1✔
63
                if (*it != 'c' && *it != 'l')
4✔
UNCOV
64
                    report_error("invalid format specifier. Expected either :nc or :nl");
×
65
                [[fallthrough]];
66

67
            case 'c':
68
                if (*it == 'c')
5✔
69
                {
70
                    set_separator(fmt::detail::string_literal<char, ',', ' '> {});
2✔
71
                    ++it;
2✔
72
                    return it;
2✔
73
                }
74
                [[fallthrough]];
75

76
            case 'l':
77
                set_separator(fmt::detail::string_literal<char, '\n'> {});
3✔
78
                ++it;
3✔
79
                return it;
4✔
80

81
            case '?':
82
                is_debug = true;
1✔
83
                set_brackets({}, {});
1✔
84
                ++it;
1✔
85
                if (it == end || *it != 's')
2✔
UNCOV
86
                    report_error("invalid format specifier. Expected :?s, not :?");
×
87
                [[fallthrough]];
88

89
            case 's':
90
                if (!is_debug)
2✔
91
                {
92
                    set_brackets(fmt::detail::string_literal<char, '"'> {},
2✔
93
                                 fmt::detail::string_literal<char, '"'> {});
1✔
94
                    set_separator({});
1✔
95
                    is_literal_str = true;
1✔
96
                }
1✔
97
                ++it;
2✔
98
                return it;
6✔
99

100
            default:
101
                break;
4✔
102
        }
4✔
103

104
        if (it != end && *it != '}')
4✔
105
        {
106
            if (*it != ':')
2✔
UNCOV
107
                report_error("invalid format specifier");
×
108
            ++it;
2✔
109
        }
2✔
110

111
        ctx.advance_to(it);
4✔
112
        return underlying_.parse(ctx);
2✔
113
    }
11✔
114

115
    template <typename Output, typename It, typename Sentinel>
116
    auto write_debug_string(Output& out, It it, Sentinel end, Ark::VM* vm_ptr) const -> Output
1✔
117
    {
1✔
118
        auto buf = fmt::basic_memory_buffer<char>();
1✔
119
        for (; it != end; ++it)
5✔
120
        {
121
            auto formatted = it->toString(*vm_ptr);
4✔
122
            buf.append(formatted);
4✔
123
        }
4✔
124
        auto specs = fmt::format_specs();
1✔
125
        specs.set_type(fmt::presentation_type::debug);
1✔
126
        return fmt::detail::write<char>(
1✔
127
            out,
1✔
128
            fmt::basic_string_view<char>(buf.data(), buf.size()),
1✔
129
            specs);
130
    }
1✔
131

132
    fmt::format_context::iterator format(const value_wrapper& value, fmt::format_context& ctx) const
20✔
133
    {
20✔
134
        auto out = ctx.out();
20✔
135
        auto it = fmt::detail::range_begin(value.value.constList());
20✔
136
        const auto end = fmt::detail::range_end(value.value.constList());
20✔
137
        if (is_debug)
20✔
138
            return write_debug_string(out, it, end, value.vm_ptr);
1✔
139

140
        if ((is_literal_str && !value.nested) || !is_literal_str)
19✔
141
            out = fmt::detail::copy<char>(opening_bracket_, out);
18✔
142

143
        for (int i = 0; it != end; ++it)
98✔
144
        {
145
            if (i > 0)
79✔
146
                out = fmt::detail::copy<char>(separator_, out);
60✔
147
            ctx.advance_to(out);
79✔
148

149
            auto&& item = *it;
79✔
150
            if (item.valueType() == Ark::ValueType::List)
79✔
151
            {
152
                // if :s, do not put surrounding "" here
153
                format({ item, value.vm_ptr, /* nested= */ true }, ctx);
9✔
154
            }
9✔
155
            else
156
            {
157
                std::string formatted = item.toString(*value.vm_ptr);
70✔
158
                out = underlying_.format(formatted, ctx);
70✔
159
            }
70✔
160

161
            ++i;
79✔
162
        }
79✔
163

164
        if ((is_literal_str && !value.nested) || !is_literal_str)
19✔
165
            out = detail::copy<char>(closing_bracket_, out);
18✔
166

167
        return out;
19✔
168
    }
20✔
169
};
170

171
namespace Ark::internal::Builtins::String
172
{
173
    /**
174
     * @name format
175
     * @brief Format a String given replacements
176
     * @details See [fmt.dev](https://fmt.dev/12.0/syntax/) for syntax.
177
     * =details-begin
178
     * In the case of lists, we have custom specifiers:
179
     * - `n` removes surrounding brackets, uses ' ' as a separator
180
     * - `?s` debug format. The list is formatted as an escaped string
181
     * - `s` string format. The list is formatted as a string
182
     * - `c` changes the separator to ', '
183
     * - `l` changes the separator to '\n'
184
     *
185
     * `n` can be combined with either `c` and `l` (which are mutually exclusive): `nc`, `nl`.
186
     *
187
     * The underlying formatter is the one of strings, so you can write `::<10` to align all elements left in a 10 char wide block each.
188
     * =details-end
189
     * @param format the String to format
190
     * @param values as any argument as you need, of any valid ArkScript type
191
     * =begin
192
     * (format "Hello {}, my name is {}" "world" "ArkScript")
193
     * # Hello world, my name is ArkScript
194
     *
195
     * (format "Test {} with {{}}" "1")
196
     * # Test 1 with {}
197
     * =end
198
     * @author https://github.com/SuperFola
199
     */
200
    Value format(std::vector<Value>& n, VM* vm)
397✔
201
    {
397✔
202
        if (n.size() < 2 || n[0].valueType() != ValueType::String)
397✔
203
            throw types::TypeCheckingError(
17✔
204
                "format",
1✔
205
                { { types::Contract { { types::Typedef("string", ValueType::String),
2✔
206
                                        types::Typedef("value", ValueType::Any, /* variadic */ true) } } } },
1✔
207
                n);
1✔
208

209
        fmt::dynamic_format_arg_store<fmt::format_context> store;
396✔
210

211
        for (auto it = n.begin() + 1, it_end = n.end(); it != it_end; ++it)
999✔
212
        {
213
            if (it->valueType() == ValueType::String)
603✔
214
                store.push_back(it->stringRef());
356✔
215
            else if (it->valueType() == ValueType::Number)
247✔
216
            {
217
                double int_part;
229✔
218
                if (std::modf(it->number(), &int_part) == 0.0)
229✔
219
                    store.push_back(static_cast<long>(it->number()));
192✔
220
                else
221
                    store.push_back(it->number());
37✔
222
            }
229✔
223
            else if (it->valueType() == ValueType::Nil)
18✔
224
                store.push_back("nil");
1✔
225
            else if (it->valueType() == ValueType::True)
17✔
226
                store.push_back("true");
1✔
227
            else if (it->valueType() == ValueType::False)
16✔
228
                store.push_back("false");
1✔
229
            else if (it->valueType() == ValueType::List)
15✔
230
                store.push_back(value_wrapper { *it, vm });
14✔
231
            else
232
                store.push_back(it->toString(*vm));
1✔
233
        }
603✔
234

235
        try
236
        {
237
            return Value(fmt::vformat(n[0].stringRef(), store));
396✔
238
        }
15✔
239
        catch (fmt::format_error& e)
240
        {
241
            throw std::runtime_error(
30✔
242
                fmt::format("format: can not format \"{}\" ({} argument{} provided) because of {}",
45✔
243
                            n[0].stringRef(),
15✔
244
                            n.size() - 1,
15✔
245
                            // if we have more than one argument (not counting the string to format), plural form
246
                            n.size() > 2 ? "s" : "",
15✔
247
                            e.what()));
15✔
248
        }
15✔
249
    }
427✔
250

251
    Value findSubStr(std::vector<Value>& n, VM* vm [[maybe_unused]])
381✔
252
    {
381✔
253
        if (!types::check(n, ValueType::String, ValueType::String) &&
381✔
254
            !types::check(n, ValueType::String, ValueType::String, ValueType::Number))
218✔
255
            throw types::TypeCheckingError(
2✔
256
                "string:find",
1✔
257
                { { types::Contract {
3✔
258
                        { types::Typedef("string", ValueType::String),
2✔
259
                          types::Typedef("substr", ValueType::String) } },
1✔
260
                    types::Contract {
1✔
261
                        { types::Typedef("string", ValueType::String),
3✔
262
                          types::Typedef("substr", ValueType::String),
1✔
263
                          types::Typedef("startIndex", ValueType::Number) } } } },
1✔
264
                n);
1✔
265

266
        const std::size_t start = n.size() == 3 ? static_cast<std::size_t>(n[2].number()) : 0;
380✔
267
        const std::size_t index = n[0].stringRef().find(n[1].stringRef(), start);
380✔
268
        if (index != std::string::npos)
380✔
269
            return Value(static_cast<int>(index));
311✔
270
        return Value(-1);
69✔
271
    }
381✔
272

273
    Value removeAtStr(std::vector<Value>& n, VM* vm [[maybe_unused]])
11✔
274
    {
11✔
275
        if (!types::check(n, ValueType::String, ValueType::Number))
11✔
276
            throw types::TypeCheckingError(
3✔
277
                "string:removeAt",
1✔
278
                { { types::Contract { { types::Typedef("string", ValueType::String), types::Typedef("index", ValueType::Number) } } } },
1✔
279
                n);
1✔
280

281
        long num = static_cast<long>(n[1].number());
10✔
282
        const auto i = static_cast<std::size_t>(num < 0 ? static_cast<long>(n[0].stringRef().size()) + num : num);
10✔
283
        if (i < n[0].stringRef().size())
10✔
284
        {
285
            n[0].stringRef().erase(i, 1);
9✔
286
            return n[0];
9✔
287
        }
288
        else
289
            throw std::runtime_error(fmt::format("string:removeAt: index {} out of range (length: {})", num, n[0].stringRef().size()));
1✔
290
    }
12✔
291

292
    Value utf8len(std::vector<Value>& n, VM* vm [[maybe_unused]])
12✔
293
    {
12✔
294
        if (!types::check(n, ValueType::String))
12✔
295
            throw types::TypeCheckingError(
2✔
296
                "string:utf8len",
1✔
297
                { { types::Contract { { types::Typedef("string", ValueType::String) } } } },
1✔
298
                n);
1✔
299

300
        const std::size_t len = utf8::length(n[0].stringRef().c_str());
11✔
301
        return Value(static_cast<double>(len));
11✔
302
    }
12✔
303

304
    Value ord(std::vector<Value>& n, VM* vm [[maybe_unused]])
7,622✔
305
    {
7,622✔
306
        if (!types::check(n, ValueType::String))
7,622✔
307
            throw types::TypeCheckingError(
4✔
308
                "string:ord",
1✔
309
                { { types::Contract { { types::Typedef("string", ValueType::String) } } } },
1✔
310
                n);
1✔
311

312
        if (const std::size_t len = utf8::length(n[0].stringRef().c_str()); len != 1)
7,623✔
313
            throw std::runtime_error(fmt::format("string:ord: invalid string '{}', expected a single character, got {}", n[0].string(), len));
2✔
314

315
        const int32_t codepoint = utf8::codepoint(n[0].stringRef().c_str());
7,619✔
316
        if (codepoint == -1)
7,619✔
UNCOV
317
            throw std::runtime_error(fmt::format("string:ord: invalid string '{}'", n[0].string()));
×
318
        return Value(codepoint);
7,619✔
319
    }
7,622✔
320

321
    // cppcheck-suppress constParameterReference
322
    Value chr(std::vector<Value>& n, VM* vm [[maybe_unused]])
2,685✔
323
    {
2,685✔
324
        if (!types::check(n, ValueType::Number))
2,685✔
325
            throw types::TypeCheckingError(
2✔
326
                "string:chr",
1✔
327
                { { types::Contract { { types::Typedef("codepoint", ValueType::Number) } } } },
1✔
328
                n);
1✔
329

330
        std::array<char, 5> utf8 {};
2,684✔
331
        utf8::codepointToUtf8(static_cast<int>(n[0].number()), utf8.data());
2,684✔
332
        return Value(std::string(utf8.data()));
2,684✔
333
    }
2,685✔
334

335
    Value setStringAt(std::vector<Value>& n, VM* vm [[maybe_unused]])
7✔
336
    {
7✔
337
        if (!types::check(n, ValueType::String, ValueType::Number, ValueType::String))
7✔
338
            throw types::TypeCheckingError(
3✔
339
                "string:setAt",
1✔
340
                { { types::Contract { { types::Typedef("string", ValueType::String),
3✔
341
                                        types::Typedef("index", ValueType::Number),
1✔
342
                                        types::Typedef("value", ValueType::String) } } } },
1✔
343
                n);
1✔
344

345
        auto& string = n[0].stringRef();
6✔
346

347
        const std::size_t size = string.size();
6✔
348
        long idx = static_cast<long>(n[1].number());
6✔
349
        idx = idx < 0 ? static_cast<long>(size) + idx : idx;
6✔
350
        if (std::cmp_greater_equal(idx, size))
6✔
351
            throw std::runtime_error(
1✔
352
                fmt::format("IndexError: string:setAt index ({}) out of range (string size: {})", idx, size));
1✔
353

354
        string[static_cast<std::size_t>(idx)] = n[2].string()[0];
5✔
355
        return n[0];
5✔
356
    }
8✔
357

358
    Value codepoints(std::vector<Value>& n, VM* vm [[maybe_unused]])
6✔
359
    {
6✔
360
        if (!types::check(n, ValueType::String))
6✔
361
            throw types::TypeCheckingError(
2✔
362
                "string:codepoints",
1✔
363
                { { types::Contract { { types::Typedef("string", ValueType::String) } } } },
1✔
364
                n);
1✔
365

366
        Value data(ValueType::List);
5✔
367
        auto it = n[0].stringRef().begin();
5✔
368
        const auto end = n[0].stringRef().end();
5✔
369
        while (it != end)
25✔
370
        {
371
            auto [next, sym] = utf8_char_t::at(it, end);
40✔
372
            data.push_back(Value(sym.codepoint()));
20✔
373
            it = next;
20✔
374
        }
20✔
375

376
        return data;
5✔
377
    }
6✔
378
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc