Coverage Report

Created: 2026-10-05 12:20

/home/runner/work/panackelty/panackelty/src/vm/builtins_text.c
Line
Count
Source (jump to first uncovered line)
1
#include "builtins_internal.h"
2
#include "utf8.h"
3
#include "value.h"
4
5
#include <stdlib.h>
6
#include <string.h>
7
8
/* Text and byte operations preserve code-point semantics and runtime bounds checks. */
9
10
Value *builtins_text_call(VM *vm, const char *name, Value **a)
11
6.15M
{
12
6.15M
    if (!strcmp(name, "len")) {
  Branch (12:9): [True: 1.64M, False: 1.42M]
  Branch (12:9): [True: 1.64M, False: 1.42M]
13
3.29M
        REQUIRE(a[0]->kind == V_STR || a[0]->kind == V_BYTES || a[0]->kind == V_ARRAY,
14
3.29M
                "VM trap: len requires Str, Bytes, or Array");
15
3.29M
        size_t n = a[0]->kind == V_STR     ? a[0]->as.bytes.characters
  Branch (15:20): [True: 992k, False: 654k]
  Branch (15:20): [True: 992k, False: 654k]
16
3.29M
                   : a[0]->kind == V_BYTES ? a[0]->as.bytes.length
  Branch (16:22): [True: 0, False: 654k]
  Branch (16:22): [True: 0, False: 654k]
17
1.30M
                                           : a[0]->as.sequence.count;
18
3.29M
        return value_size(n);
19
3.29M
    }
20
21
2.85M
    if (!strcmp(name, "concat")) {
  Branch (21:9): [True: 138k, False: 1.28M]
  Branch (21:9): [True: 138k, False: 1.28M]
22
276k
        REQUIRE(a[0]->kind == V_ARRAY && a[1]->kind == V_ARRAY, "VM trap: concat requires arrays");
23
276k
        size_t n = a[0]->as.sequence.count, m = a[1]->as.sequence.count;
24
276k
        Value **items = malloc((n + m) * sizeof(Value *));
25
276k
        if (!items && n + m) {
  Branch (25:13): [True: 0, False: 138k]
  Branch (25:23): [True: 0, False: 0]
  Branch (25:13): [True: 0, False: 138k]
  Branch (25:23): [True: 0, False: 0]
26
0
            return NULL;
27
0
        }
28
276k
        memcpy(items, a[0]->as.sequence.items, n * sizeof(Value *));
29
276k
        memcpy(items + n, a[1]->as.sequence.items, m * sizeof(Value *));
30
276k
        Value *v = value_sequence(V_ARRAY, items, n + m);
31
276k
        free(items);
32
276k
        return v;
33
276k
    }
34
35
2.57M
    if (!strcmp(name, "slice")) {
  Branch (35:9): [True: 53.1k, False: 1.23M]
  Branch (35:9): [True: 53.1k, False: 1.23M]
36
106k
        REQUIRE(a[0]->kind == V_STR && a[1]->kind == V_NAT && a[2]->kind == V_NAT,
37
106k
                "VM trap: slice requires Str and Nat bounds");
38
106k
        size_t start, end, bs, be;
39
106k
        if (!value_index(a[1], &start) || !value_index(a[2], &end) || start > end) {
  Branch (39:13): [True: 0, False: 53.1k]
  Branch (39:43): [True: 0, False: 53.1k]
  Branch (39:71): [True: 1, False: 53.1k]
  Branch (39:13): [True: 0, False: 53.1k]
  Branch (39:43): [True: 0, False: 53.1k]
  Branch (39:71): [True: 1, False: 53.1k]
40
2
            vm->error = "VM trap: invalid string slice";
41
2
            return NULL;
42
2
        }
43
106k
        size_t count = a[0]->as.bytes.characters;
44
106k
        if (end > count) {
  Branch (44:13): [True: 3, False: 53.1k]
  Branch (44:13): [True: 3, False: 53.1k]
45
6
            vm->error = "VM trap: invalid string slice";
46
6
            return NULL;
47
6
        }
48
106k
        if (start == count) {
  Branch (48:13): [True: 344, False: 52.8k]
  Branch (48:13): [True: 344, False: 52.8k]
49
688
            bs = a[0]->as.bytes.length;
50
105k
        } else if (!utf8_offset(a[0], start, &bs, &be)) {
  Branch (50:20): [True: 0, False: 52.8k]
  Branch (50:20): [True: 0, False: 52.8k]
51
0
            return NULL;
52
0
        }
53
106k
        if (end == count) {
  Branch (53:13): [True: 4.30k, False: 48.8k]
  Branch (53:13): [True: 4.30k, False: 48.8k]
54
8.61k
            be = a[0]->as.bytes.length;
55
97.7k
        } else {
56
97.7k
            size_t ignored;
57
97.7k
            if (!utf8_offset(a[0], end, &be, &ignored)) {
  Branch (57:17): [True: 0, False: 48.8k]
  Branch (57:17): [True: 0, False: 48.8k]
58
0
                return NULL;
59
0
            }
60
97.7k
        }
61
106k
        return value_data(V_STR, a[0]->as.bytes.data + bs, be - bs);
62
106k
    }
63
64
2.47M
    if (!strcmp(name, "starts_with") || !strcmp(name, "starts_with_at")) {
  Branch (64:9): [True: 26.1k, False: 1.21M]
  Branch (64:41): [True: 487k, False: 723k]
  Branch (64:9): [True: 26.1k, False: 1.21M]
  Branch (64:41): [True: 487k, False: 723k]
65
1.02M
        REQUIRE(a[0]->kind == V_STR && a[1]->kind == V_STR, "VM trap: starts_with requires Str");
66
1.02M
        if (!strcmp(name, "starts_with_at")) {
  Branch (66:13): [True: 487k, False: 26.1k]
  Branch (66:13): [True: 487k, False: 26.1k]
67
974k
            REQUIRE(a[2]->kind == V_NAT, "VM trap: starts_with_at requires Nat offset");
68
974k
        }
69
1.02M
        size_t offset = 0, bs = 0, be;
70
1.02M
        if (!strcmp(name, "starts_with_at")) {
  Branch (70:13): [True: 487k, False: 26.1k]
  Branch (70:13): [True: 487k, False: 26.1k]
71
974k
            if (!value_index(a[2], &offset)) {
  Branch (71:17): [True: 1, False: 487k]
  Branch (71:17): [True: 1, False: 487k]
72
2
                return value_bool(false);
73
2
            }
74
974k
            if (offset == a[0]->as.bytes.characters) {
  Branch (74:17): [True: 3, False: 487k]
  Branch (74:17): [True: 3, False: 487k]
75
6
                bs = a[0]->as.bytes.length;
76
974k
            } else if (!utf8_offset(a[0], offset, &bs, &be)) {
  Branch (76:24): [True: 2, False: 487k]
  Branch (76:24): [True: 2, False: 487k]
77
4
                return value_bool(false);
78
4
            }
79
974k
        }
80
1.02M
        bool result = a[1]->as.bytes.length <= a[0]->as.bytes.length - bs &&
  Branch (80:23): [True: 489k, False: 23.4k]
  Branch (80:23): [True: 489k, False: 23.4k]
81
1.02M
                      !memcmp(a[0]->as.bytes.data + bs, a[1]->as.bytes.data, a[1]->as.bytes.length);
  Branch (81:23): [True: 3.86k, False: 485k]
  Branch (81:23): [True: 3.86k, False: 485k]
82
1.02M
        return value_bool(result);
83
1.02M
    }
84
85
1.44M
    if (!strcmp(name, "reverse")) {
  Branch (85:9): [True: 6, False: 723k]
  Branch (85:9): [True: 6, False: 723k]
86
12
        REQUIRE(a[0]->kind == V_STR, "VM trap: reverse requires Str");
87
12
        size_t n = a[0]->as.bytes.length, source = 0, destination = n;
88
12
        uint8_t *data = malloc(n ? n : 1);
  Branch (88:32): [True: 6, False: 0]
  Branch (88:32): [True: 6, False: 0]
89
12
        if (!data) {
  Branch (89:13): [True: 0, False: 6]
  Branch (89:13): [True: 0, False: 6]
90
0
            return NULL;
91
0
        }
92
52
        while (source < n) {
  Branch (92:16): [True: 20, False: 6]
  Branch (92:16): [True: 20, False: 6]
93
40
            uint8_t c = a[0]->as.bytes.data[source];
94
40
            size_t width = c < 0x80 ? 1 : (c & 0xe0) == 0xc0 ? 2 : (c & 0xf0) == 0xe0 ? 3 : 4;
  Branch (94:28): [True: 8, False: 12]
  Branch (94:43): [True: 6, False: 6]
  Branch (94:68): [True: 1, False: 5]
  Branch (94:28): [True: 8, False: 12]
  Branch (94:43): [True: 6, False: 6]
  Branch (94:68): [True: 1, False: 5]
95
40
            destination -= width;
96
40
            memcpy(data + destination, a[0]->as.bytes.data + source, width);
97
40
            source += width;
98
40
        }
99
12
        Value *v = value_data(V_STR, data, n);
100
12
        free(data);
101
12
        return v;
102
12
    }
103
104
1.44M
    if (!strcmp(name, "is_digit") || !strcmp(name, "is_letter") || !strcmp(name, "is_whitespace")) {
  Branch (104:9): [True: 109k, False: 613k]
  Branch (104:38): [True: 252k, False: 360k]
  Branch (104:68): [True: 158k, False: 202k]
  Branch (104:9): [True: 109k, False: 613k]
  Branch (104:38): [True: 252k, False: 360k]
  Branch (104:68): [True: 158k, False: 202k]
105
1.04M
        REQUIRE(a[0]->kind == V_STR, "VM trap: character class requires Str");
106
1.04M
        uint8_t c = a[0]->as.bytes.length == 1 ? a[0]->as.bytes.data[0] : 0;
  Branch (106:21): [True: 520k, False: 1]
  Branch (106:21): [True: 520k, False: 1]
107
1.04M
        bool result = !strcmp(name, "is_digit") ? c >= '0' && c <= '9'
  Branch (107:23): [True: 109k, False: 411k]
  Branch (107:51): [True: 64.2k, False: 45.2k]
  Branch (107:63): [True: 12.4k, False: 51.7k]
  Branch (107:23): [True: 109k, False: 411k]
  Branch (107:51): [True: 64.2k, False: 45.2k]
  Branch (107:63): [True: 12.4k, False: 51.7k]
108
1.04M
                      : !strcmp(name, "is_letter")
  Branch (108:25): [True: 252k, False: 158k]
  Branch (108:25): [True: 252k, False: 158k]
109
822k
                          ? (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
  Branch (109:30): [True: 201k, False: 51.3k]
  Branch (109:42): [True: 11.8k, False: 189k]
  Branch (109:56): [True: 177k, False: 63.9k]
  Branch (109:68): [True: 171k, False: 5.78k]
  Branch (109:30): [True: 201k, False: 51.3k]
  Branch (109:42): [True: 11.8k, False: 189k]
  Branch (109:56): [True: 177k, False: 63.9k]
  Branch (109:68): [True: 171k, False: 5.78k]
110
822k
                          : c == ' ' || c == '\t' || c == '\r' || c == '\n';
  Branch (110:29): [True: 55.8k, False: 102k]
  Branch (110:41): [True: 0, False: 102k]
  Branch (110:54): [True: 0, False: 102k]
  Branch (110:67): [True: 173, False: 102k]
  Branch (110:29): [True: 55.8k, False: 102k]
  Branch (110:41): [True: 0, False: 102k]
  Branch (110:54): [True: 0, False: 102k]
  Branch (110:67): [True: 173, False: 102k]
111
1.04M
        return value_bool(result);
112
1.04M
    }
113
114
404k
    if (!strcmp(name, "byte_append")) {
  Branch (114:9): [True: 97.9k, False: 104k]
  Branch (114:9): [True: 97.9k, False: 104k]
115
195k
        REQUIRE(a[0]->kind == V_BYTES && a[1]->kind == V_NAT,
116
195k
                "VM trap: byte_append requires Bytes and Nat");
117
195k
        size_t byte;
118
195k
        if (!value_index(a[1], &byte) || byte > 255) {
  Branch (118:13): [True: 0, False: 97.9k]
  Branch (118:42): [True: 2, False: 97.9k]
  Branch (118:13): [True: 0, False: 97.9k]
  Branch (118:42): [True: 2, False: 97.9k]
119
4
            vm->error = "VM trap: byte value is outside 0..255";
120
4
            return NULL;
121
4
        }
122
195k
        size_t n = a[0]->as.bytes.length;
123
195k
        uint8_t *data = malloc(n + 1);
124
195k
        if (!data) {
  Branch (124:13): [True: 0, False: 97.9k]
  Branch (124:13): [True: 0, False: 97.9k]
125
0
            return NULL;
126
0
        }
127
195k
        memcpy(data, a[0]->as.bytes.data, n);
128
195k
        data[n] = (uint8_t)byte;
129
195k
        Value *v = value_data(V_BYTES, data, n + 1);
130
195k
        free(data);
131
195k
        return v;
132
195k
    }
133
134
208k
    if (!strcmp(name, "bytes_concat")) {
  Branch (134:9): [True: 62.0k, False: 42.3k]
  Branch (134:9): [True: 62.0k, False: 42.3k]
135
124k
        REQUIRE(a[0]->kind == V_BYTES && a[1]->kind == V_BYTES,
136
124k
                "VM trap: bytes_concat requires Bytes");
137
124k
        size_t n = a[0]->as.bytes.length, m = a[1]->as.bytes.length;
138
124k
        uint8_t *data = malloc(n + m);
139
124k
        if (!data && n + m) {
  Branch (139:13): [True: 0, False: 62.0k]
  Branch (139:22): [True: 0, False: 0]
  Branch (139:13): [True: 0, False: 62.0k]
  Branch (139:22): [True: 0, False: 0]
140
0
            return NULL;
141
0
        }
142
124k
        memcpy(data, a[0]->as.bytes.data, n);
143
124k
        memcpy(data + n, a[1]->as.bytes.data, m);
144
124k
        Value *v = value_data(V_BYTES, data, n + m);
145
124k
        free(data);
146
124k
        return v;
147
124k
    }
148
149
84.6k
    if (!strcmp(name, "byte_len")) {
  Branch (149:9): [True: 18.8k, False: 23.4k]
  Branch (149:9): [True: 18.8k, False: 23.4k]
150
37.7k
        REQUIRE(a[0]->kind == V_BYTES, "VM trap: byte_len requires Bytes");
151
37.7k
        return value_size(a[0]->as.bytes.length);
152
37.7k
    }
153
154
46.9k
    if (!strcmp(name, "byte_get")) {
  Branch (154:9): [True: 5.55k, False: 17.9k]
  Branch (154:9): [True: 5.55k, False: 17.9k]
155
11.1k
        REQUIRE(a[0]->kind == V_BYTES && a[1]->kind == V_NAT,
156
11.1k
                "VM trap: byte_get requires Bytes and Nat");
157
11.1k
        size_t i;
158
11.1k
        if (!value_index(a[1], &i) || i >= a[0]->as.bytes.length) {
  Branch (158:13): [True: 0, False: 5.55k]
  Branch (158:39): [True: 0, False: 5.55k]
  Branch (158:13): [True: 0, False: 5.55k]
  Branch (158:39): [True: 0, False: 5.55k]
159
0
            vm->error = "VM trap: byte index is out of bounds";
160
0
            return NULL;
161
0
        }
162
11.1k
        return value_size(a[0]->as.bytes.data[i]);
163
11.1k
    }
164
165
35.8k
    if (!strcmp(name, "utf8_encode")) {
  Branch (165:9): [True: 14.3k, False: 3.55k]
  Branch (165:9): [True: 14.3k, False: 3.55k]
166
28.7k
        REQUIRE(a[0]->kind == V_STR, "VM trap: utf8_encode requires Str");
167
28.7k
        return value_data(V_BYTES, a[0]->as.bytes.data, a[0]->as.bytes.length);
168
28.7k
    }
169
170
7.11k
    if (!strcmp(name, "utf8_decode")) {
  Branch (170:9): [True: 550, False: 3.00k]
  Branch (170:9): [True: 550, False: 3.00k]
171
1.10k
        REQUIRE(a[0]->kind == V_BYTES, "VM trap: utf8_decode requires Bytes");
172
1.09k
        if (!utf8(a[0]->as.bytes.data, a[0]->as.bytes.length)) {
  Branch (172:13): [True: 2, False: 547]
  Branch (172:13): [True: 2, False: 547]
173
4
            vm->error = "VM trap: invalid UTF-8";
174
4
            return NULL;
175
4
        }
176
1.09k
        return value_data(V_STR, a[0]->as.bytes.data, a[0]->as.bytes.length);
177
1.09k
    }
178
179
6.01k
    if (!strcmp(name, "nat_from_str")) {
  Branch (179:9): [True: 3.00k, False: 0]
  Branch (179:9): [True: 3.00k, False: 0]
180
6.01k
        REQUIRE(a[0]->kind == V_STR, "VM trap: nat_from_str requires Str");
181
6.01k
        if (!a[0]->as.bytes.length) {
  Branch (181:13): [True: 0, False: 3.00k]
  Branch (181:13): [True: 0, False: 3.00k]
182
0
            vm->error = "VM trap: text is not a Nat";
183
0
            return NULL;
184
0
        }
185
27.6k
        for (size_t i = 0; i < a[0]->as.bytes.length; i++) {
  Branch (185:28): [True: 10.8k, False: 3.00k]
  Branch (185:28): [True: 10.8k, False: 3.00k]
186
21.6k
            if (a[0]->as.bytes.data[i] < '0' || a[0]->as.bytes.data[i] > '9') {
  Branch (186:17): [True: 0, False: 10.8k]
  Branch (186:49): [True: 0, False: 10.8k]
  Branch (186:17): [True: 0, False: 10.8k]
  Branch (186:49): [True: 0, False: 10.8k]
187
0
                vm->error = "VM trap: text is not a Nat";
188
0
                return NULL;
189
0
            }
190
21.6k
        }
191
6.01k
        PnBigInt n;
192
6.01k
        if (!pn_big_from_digits(&n, (char *)a[0]->as.bytes.data, a[0]->as.bytes.length, 1)) {
  Branch (192:13): [True: 0, False: 3.00k]
  Branch (192:13): [True: 0, False: 3.00k]
193
0
            return NULL;
194
0
        }
195
6.01k
        Value *v = value_big(V_NAT, &n);
196
6.01k
        pn_big_free(&n);
197
6.01k
        return v;
198
6.01k
    }
199
0
    vm->error = "VM trap: unknown builtin";
200
0
    return NULL;
201
6.01k
}
builtins_text_call
Line
Count
Source
11
3.07M
{
12
3.07M
    if (!strcmp(name, "len")) {
  Branch (12:9): [True: 1.64M, False: 1.42M]
13
1.64M
        REQUIRE(a[0]->kind == V_STR || a[0]->kind == V_BYTES || a[0]->kind == V_ARRAY,
14
1.64M
                "VM trap: len requires Str, Bytes, or Array");
15
1.64M
        size_t n = a[0]->kind == V_STR     ? a[0]->as.bytes.characters
  Branch (15:20): [True: 992k, False: 654k]
16
1.64M
                   : a[0]->kind == V_BYTES ? a[0]->as.bytes.length
  Branch (16:22): [True: 0, False: 654k]
17
654k
                                           : a[0]->as.sequence.count;
18
1.64M
        return value_size(n);
19
1.64M
    }
20
21
1.42M
    if (!strcmp(name, "concat")) {
  Branch (21:9): [True: 138k, False: 1.28M]
22
138k
        REQUIRE(a[0]->kind == V_ARRAY && a[1]->kind == V_ARRAY, "VM trap: concat requires arrays");
23
138k
        size_t n = a[0]->as.sequence.count, m = a[1]->as.sequence.count;
24
138k
        Value **items = malloc((n + m) * sizeof(Value *));
25
138k
        if (!items && n + m) {
  Branch (25:13): [True: 0, False: 138k]
  Branch (25:23): [True: 0, False: 0]
26
0
            return NULL;
27
0
        }
28
138k
        memcpy(items, a[0]->as.sequence.items, n * sizeof(Value *));
29
138k
        memcpy(items + n, a[1]->as.sequence.items, m * sizeof(Value *));
30
138k
        Value *v = value_sequence(V_ARRAY, items, n + m);
31
138k
        free(items);
32
138k
        return v;
33
138k
    }
34
35
1.28M
    if (!strcmp(name, "slice")) {
  Branch (35:9): [True: 53.1k, False: 1.23M]
36
53.1k
        REQUIRE(a[0]->kind == V_STR && a[1]->kind == V_NAT && a[2]->kind == V_NAT,
37
53.1k
                "VM trap: slice requires Str and Nat bounds");
38
53.1k
        size_t start, end, bs, be;
39
53.1k
        if (!value_index(a[1], &start) || !value_index(a[2], &end) || start > end) {
  Branch (39:13): [True: 0, False: 53.1k]
  Branch (39:43): [True: 0, False: 53.1k]
  Branch (39:71): [True: 1, False: 53.1k]
40
1
            vm->error = "VM trap: invalid string slice";
41
1
            return NULL;
42
1
        }
43
53.1k
        size_t count = a[0]->as.bytes.characters;
44
53.1k
        if (end > count) {
  Branch (44:13): [True: 3, False: 53.1k]
45
3
            vm->error = "VM trap: invalid string slice";
46
3
            return NULL;
47
3
        }
48
53.1k
        if (start == count) {
  Branch (48:13): [True: 344, False: 52.8k]
49
344
            bs = a[0]->as.bytes.length;
50
52.8k
        } else if (!utf8_offset(a[0], start, &bs, &be)) {
  Branch (50:20): [True: 0, False: 52.8k]
51
0
            return NULL;
52
0
        }
53
53.1k
        if (end == count) {
  Branch (53:13): [True: 4.30k, False: 48.8k]
54
4.30k
            be = a[0]->as.bytes.length;
55
48.8k
        } else {
56
48.8k
            size_t ignored;
57
48.8k
            if (!utf8_offset(a[0], end, &be, &ignored)) {
  Branch (57:17): [True: 0, False: 48.8k]
58
0
                return NULL;
59
0
            }
60
48.8k
        }
61
53.1k
        return value_data(V_STR, a[0]->as.bytes.data + bs, be - bs);
62
53.1k
    }
63
64
1.23M
    if (!strcmp(name, "starts_with") || !strcmp(name, "starts_with_at")) {
  Branch (64:9): [True: 26.1k, False: 1.21M]
  Branch (64:41): [True: 487k, False: 723k]
65
513k
        REQUIRE(a[0]->kind == V_STR && a[1]->kind == V_STR, "VM trap: starts_with requires Str");
66
513k
        if (!strcmp(name, "starts_with_at")) {
  Branch (66:13): [True: 487k, False: 26.1k]
67
487k
            REQUIRE(a[2]->kind == V_NAT, "VM trap: starts_with_at requires Nat offset");
68
487k
        }
69
513k
        size_t offset = 0, bs = 0, be;
70
513k
        if (!strcmp(name, "starts_with_at")) {
  Branch (70:13): [True: 487k, False: 26.1k]
71
487k
            if (!value_index(a[2], &offset)) {
  Branch (71:17): [True: 1, False: 487k]
72
1
                return value_bool(false);
73
1
            }
74
487k
            if (offset == a[0]->as.bytes.characters) {
  Branch (74:17): [True: 3, False: 487k]
75
3
                bs = a[0]->as.bytes.length;
76
487k
            } else if (!utf8_offset(a[0], offset, &bs, &be)) {
  Branch (76:24): [True: 2, False: 487k]
77
2
                return value_bool(false);
78
2
            }
79
487k
        }
80
513k
        bool result = a[1]->as.bytes.length <= a[0]->as.bytes.length - bs &&
  Branch (80:23): [True: 489k, False: 23.4k]
81
513k
                      !memcmp(a[0]->as.bytes.data + bs, a[1]->as.bytes.data, a[1]->as.bytes.length);
  Branch (81:23): [True: 3.86k, False: 485k]
82
513k
        return value_bool(result);
83
513k
    }
84
85
723k
    if (!strcmp(name, "reverse")) {
  Branch (85:9): [True: 6, False: 723k]
86
6
        REQUIRE(a[0]->kind == V_STR, "VM trap: reverse requires Str");
87
6
        size_t n = a[0]->as.bytes.length, source = 0, destination = n;
88
6
        uint8_t *data = malloc(n ? n : 1);
  Branch (88:32): [True: 6, False: 0]
89
6
        if (!data) {
  Branch (89:13): [True: 0, False: 6]
90
0
            return NULL;
91
0
        }
92
26
        while (source < n) {
  Branch (92:16): [True: 20, False: 6]
93
20
            uint8_t c = a[0]->as.bytes.data[source];
94
20
            size_t width = c < 0x80 ? 1 : (c & 0xe0) == 0xc0 ? 2 : (c & 0xf0) == 0xe0 ? 3 : 4;
  Branch (94:28): [True: 8, False: 12]
  Branch (94:43): [True: 6, False: 6]
  Branch (94:68): [True: 1, False: 5]
95
20
            destination -= width;
96
20
            memcpy(data + destination, a[0]->as.bytes.data + source, width);
97
20
            source += width;
98
20
        }
99
6
        Value *v = value_data(V_STR, data, n);
100
6
        free(data);
101
6
        return v;
102
6
    }
103
104
723k
    if (!strcmp(name, "is_digit") || !strcmp(name, "is_letter") || !strcmp(name, "is_whitespace")) {
  Branch (104:9): [True: 109k, False: 613k]
  Branch (104:38): [True: 252k, False: 360k]
  Branch (104:68): [True: 158k, False: 202k]
105
520k
        REQUIRE(a[0]->kind == V_STR, "VM trap: character class requires Str");
106
520k
        uint8_t c = a[0]->as.bytes.length == 1 ? a[0]->as.bytes.data[0] : 0;
  Branch (106:21): [True: 520k, False: 1]
107
520k
        bool result = !strcmp(name, "is_digit") ? c >= '0' && c <= '9'
  Branch (107:23): [True: 109k, False: 411k]
  Branch (107:51): [True: 64.2k, False: 45.2k]
  Branch (107:63): [True: 12.4k, False: 51.7k]
108
520k
                      : !strcmp(name, "is_letter")
  Branch (108:25): [True: 252k, False: 158k]
109
411k
                          ? (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
  Branch (109:30): [True: 201k, False: 51.3k]
  Branch (109:42): [True: 11.8k, False: 189k]
  Branch (109:56): [True: 177k, False: 63.9k]
  Branch (109:68): [True: 171k, False: 5.78k]
110
411k
                          : c == ' ' || c == '\t' || c == '\r' || c == '\n';
  Branch (110:29): [True: 55.8k, False: 102k]
  Branch (110:41): [True: 0, False: 102k]
  Branch (110:54): [True: 0, False: 102k]
  Branch (110:67): [True: 173, False: 102k]
111
520k
        return value_bool(result);
112
520k
    }
113
114
202k
    if (!strcmp(name, "byte_append")) {
  Branch (114:9): [True: 97.9k, False: 104k]
115
97.9k
        REQUIRE(a[0]->kind == V_BYTES && a[1]->kind == V_NAT,
116
97.9k
                "VM trap: byte_append requires Bytes and Nat");
117
97.9k
        size_t byte;
118
97.9k
        if (!value_index(a[1], &byte) || byte > 255) {
  Branch (118:13): [True: 0, False: 97.9k]
  Branch (118:42): [True: 2, False: 97.9k]
119
2
            vm->error = "VM trap: byte value is outside 0..255";
120
2
            return NULL;
121
2
        }
122
97.9k
        size_t n = a[0]->as.bytes.length;
123
97.9k
        uint8_t *data = malloc(n + 1);
124
97.9k
        if (!data) {
  Branch (124:13): [True: 0, False: 97.9k]
125
0
            return NULL;
126
0
        }
127
97.9k
        memcpy(data, a[0]->as.bytes.data, n);
128
97.9k
        data[n] = (uint8_t)byte;
129
97.9k
        Value *v = value_data(V_BYTES, data, n + 1);
130
97.9k
        free(data);
131
97.9k
        return v;
132
97.9k
    }
133
134
104k
    if (!strcmp(name, "bytes_concat")) {
  Branch (134:9): [True: 62.0k, False: 42.3k]
135
62.0k
        REQUIRE(a[0]->kind == V_BYTES && a[1]->kind == V_BYTES,
136
62.0k
                "VM trap: bytes_concat requires Bytes");
137
62.0k
        size_t n = a[0]->as.bytes.length, m = a[1]->as.bytes.length;
138
62.0k
        uint8_t *data = malloc(n + m);
139
62.0k
        if (!data && n + m) {
  Branch (139:13): [True: 0, False: 62.0k]
  Branch (139:22): [True: 0, False: 0]
140
0
            return NULL;
141
0
        }
142
62.0k
        memcpy(data, a[0]->as.bytes.data, n);
143
62.0k
        memcpy(data + n, a[1]->as.bytes.data, m);
144
62.0k
        Value *v = value_data(V_BYTES, data, n + m);
145
62.0k
        free(data);
146
62.0k
        return v;
147
62.0k
    }
148
149
42.3k
    if (!strcmp(name, "byte_len")) {
  Branch (149:9): [True: 18.8k, False: 23.4k]
150
18.8k
        REQUIRE(a[0]->kind == V_BYTES, "VM trap: byte_len requires Bytes");
151
18.8k
        return value_size(a[0]->as.bytes.length);
152
18.8k
    }
153
154
23.4k
    if (!strcmp(name, "byte_get")) {
  Branch (154:9): [True: 5.55k, False: 17.9k]
155
5.55k
        REQUIRE(a[0]->kind == V_BYTES && a[1]->kind == V_NAT,
156
5.55k
                "VM trap: byte_get requires Bytes and Nat");
157
5.55k
        size_t i;
158
5.55k
        if (!value_index(a[1], &i) || i >= a[0]->as.bytes.length) {
  Branch (158:13): [True: 0, False: 5.55k]
  Branch (158:39): [True: 0, False: 5.55k]
159
0
            vm->error = "VM trap: byte index is out of bounds";
160
0
            return NULL;
161
0
        }
162
5.55k
        return value_size(a[0]->as.bytes.data[i]);
163
5.55k
    }
164
165
17.9k
    if (!strcmp(name, "utf8_encode")) {
  Branch (165:9): [True: 14.3k, False: 3.55k]
166
14.3k
        REQUIRE(a[0]->kind == V_STR, "VM trap: utf8_encode requires Str");
167
14.3k
        return value_data(V_BYTES, a[0]->as.bytes.data, a[0]->as.bytes.length);
168
14.3k
    }
169
170
3.55k
    if (!strcmp(name, "utf8_decode")) {
  Branch (170:9): [True: 550, False: 3.00k]
171
550
        REQUIRE(a[0]->kind == V_BYTES, "VM trap: utf8_decode requires Bytes");
172
549
        if (!utf8(a[0]->as.bytes.data, a[0]->as.bytes.length)) {
  Branch (172:13): [True: 2, False: 547]
173
2
            vm->error = "VM trap: invalid UTF-8";
174
2
            return NULL;
175
2
        }
176
547
        return value_data(V_STR, a[0]->as.bytes.data, a[0]->as.bytes.length);
177
549
    }
178
179
3.00k
    if (!strcmp(name, "nat_from_str")) {
  Branch (179:9): [True: 3.00k, False: 0]
180
3.00k
        REQUIRE(a[0]->kind == V_STR, "VM trap: nat_from_str requires Str");
181
3.00k
        if (!a[0]->as.bytes.length) {
  Branch (181:13): [True: 0, False: 3.00k]
182
0
            vm->error = "VM trap: text is not a Nat";
183
0
            return NULL;
184
0
        }
185
13.8k
        for (size_t i = 0; i < a[0]->as.bytes.length; i++) {
  Branch (185:28): [True: 10.8k, False: 3.00k]
186
10.8k
            if (a[0]->as.bytes.data[i] < '0' || a[0]->as.bytes.data[i] > '9') {
  Branch (186:17): [True: 0, False: 10.8k]
  Branch (186:49): [True: 0, False: 10.8k]
187
0
                vm->error = "VM trap: text is not a Nat";
188
0
                return NULL;
189
0
            }
190
10.8k
        }
191
3.00k
        PnBigInt n;
192
3.00k
        if (!pn_big_from_digits(&n, (char *)a[0]->as.bytes.data, a[0]->as.bytes.length, 1)) {
  Branch (192:13): [True: 0, False: 3.00k]
193
0
            return NULL;
194
0
        }
195
3.00k
        Value *v = value_big(V_NAT, &n);
196
3.00k
        pn_big_free(&n);
197
3.00k
        return v;
198
3.00k
    }
199
0
    vm->error = "VM trap: unknown builtin";
200
0
    return NULL;
201
3.00k
}
builtins_text_call
Line
Count
Source
11
3.07M
{
12
3.07M
    if (!strcmp(name, "len")) {
  Branch (12:9): [True: 1.64M, False: 1.42M]
13
1.64M
        REQUIRE(a[0]->kind == V_STR || a[0]->kind == V_BYTES || a[0]->kind == V_ARRAY,
14
1.64M
                "VM trap: len requires Str, Bytes, or Array");
15
1.64M
        size_t n = a[0]->kind == V_STR     ? a[0]->as.bytes.characters
  Branch (15:20): [True: 992k, False: 654k]
16
1.64M
                   : a[0]->kind == V_BYTES ? a[0]->as.bytes.length
  Branch (16:22): [True: 0, False: 654k]
17
654k
                                           : a[0]->as.sequence.count;
18
1.64M
        return value_size(n);
19
1.64M
    }
20
21
1.42M
    if (!strcmp(name, "concat")) {
  Branch (21:9): [True: 138k, False: 1.28M]
22
138k
        REQUIRE(a[0]->kind == V_ARRAY && a[1]->kind == V_ARRAY, "VM trap: concat requires arrays");
23
138k
        size_t n = a[0]->as.sequence.count, m = a[1]->as.sequence.count;
24
138k
        Value **items = malloc((n + m) * sizeof(Value *));
25
138k
        if (!items && n + m) {
  Branch (25:13): [True: 0, False: 138k]
  Branch (25:23): [True: 0, False: 0]
26
0
            return NULL;
27
0
        }
28
138k
        memcpy(items, a[0]->as.sequence.items, n * sizeof(Value *));
29
138k
        memcpy(items + n, a[1]->as.sequence.items, m * sizeof(Value *));
30
138k
        Value *v = value_sequence(V_ARRAY, items, n + m);
31
138k
        free(items);
32
138k
        return v;
33
138k
    }
34
35
1.28M
    if (!strcmp(name, "slice")) {
  Branch (35:9): [True: 53.1k, False: 1.23M]
36
53.1k
        REQUIRE(a[0]->kind == V_STR && a[1]->kind == V_NAT && a[2]->kind == V_NAT,
37
53.1k
                "VM trap: slice requires Str and Nat bounds");
38
53.1k
        size_t start, end, bs, be;
39
53.1k
        if (!value_index(a[1], &start) || !value_index(a[2], &end) || start > end) {
  Branch (39:13): [True: 0, False: 53.1k]
  Branch (39:43): [True: 0, False: 53.1k]
  Branch (39:71): [True: 1, False: 53.1k]
40
1
            vm->error = "VM trap: invalid string slice";
41
1
            return NULL;
42
1
        }
43
53.1k
        size_t count = a[0]->as.bytes.characters;
44
53.1k
        if (end > count) {
  Branch (44:13): [True: 3, False: 53.1k]
45
3
            vm->error = "VM trap: invalid string slice";
46
3
            return NULL;
47
3
        }
48
53.1k
        if (start == count) {
  Branch (48:13): [True: 344, False: 52.8k]
49
344
            bs = a[0]->as.bytes.length;
50
52.8k
        } else if (!utf8_offset(a[0], start, &bs, &be)) {
  Branch (50:20): [True: 0, False: 52.8k]
51
0
            return NULL;
52
0
        }
53
53.1k
        if (end == count) {
  Branch (53:13): [True: 4.30k, False: 48.8k]
54
4.30k
            be = a[0]->as.bytes.length;
55
48.8k
        } else {
56
48.8k
            size_t ignored;
57
48.8k
            if (!utf8_offset(a[0], end, &be, &ignored)) {
  Branch (57:17): [True: 0, False: 48.8k]
58
0
                return NULL;
59
0
            }
60
48.8k
        }
61
53.1k
        return value_data(V_STR, a[0]->as.bytes.data + bs, be - bs);
62
53.1k
    }
63
64
1.23M
    if (!strcmp(name, "starts_with") || !strcmp(name, "starts_with_at")) {
  Branch (64:9): [True: 26.1k, False: 1.21M]
  Branch (64:41): [True: 487k, False: 723k]
65
513k
        REQUIRE(a[0]->kind == V_STR && a[1]->kind == V_STR, "VM trap: starts_with requires Str");
66
513k
        if (!strcmp(name, "starts_with_at")) {
  Branch (66:13): [True: 487k, False: 26.1k]
67
487k
            REQUIRE(a[2]->kind == V_NAT, "VM trap: starts_with_at requires Nat offset");
68
487k
        }
69
513k
        size_t offset = 0, bs = 0, be;
70
513k
        if (!strcmp(name, "starts_with_at")) {
  Branch (70:13): [True: 487k, False: 26.1k]
71
487k
            if (!value_index(a[2], &offset)) {
  Branch (71:17): [True: 1, False: 487k]
72
1
                return value_bool(false);
73
1
            }
74
487k
            if (offset == a[0]->as.bytes.characters) {
  Branch (74:17): [True: 3, False: 487k]
75
3
                bs = a[0]->as.bytes.length;
76
487k
            } else if (!utf8_offset(a[0], offset, &bs, &be)) {
  Branch (76:24): [True: 2, False: 487k]
77
2
                return value_bool(false);
78
2
            }
79
487k
        }
80
513k
        bool result = a[1]->as.bytes.length <= a[0]->as.bytes.length - bs &&
  Branch (80:23): [True: 489k, False: 23.4k]
81
513k
                      !memcmp(a[0]->as.bytes.data + bs, a[1]->as.bytes.data, a[1]->as.bytes.length);
  Branch (81:23): [True: 3.86k, False: 485k]
82
513k
        return value_bool(result);
83
513k
    }
84
85
723k
    if (!strcmp(name, "reverse")) {
  Branch (85:9): [True: 6, False: 723k]
86
6
        REQUIRE(a[0]->kind == V_STR, "VM trap: reverse requires Str");
87
6
        size_t n = a[0]->as.bytes.length, source = 0, destination = n;
88
6
        uint8_t *data = malloc(n ? n : 1);
  Branch (88:32): [True: 6, False: 0]
89
6
        if (!data) {
  Branch (89:13): [True: 0, False: 6]
90
0
            return NULL;
91
0
        }
92
26
        while (source < n) {
  Branch (92:16): [True: 20, False: 6]
93
20
            uint8_t c = a[0]->as.bytes.data[source];
94
20
            size_t width = c < 0x80 ? 1 : (c & 0xe0) == 0xc0 ? 2 : (c & 0xf0) == 0xe0 ? 3 : 4;
  Branch (94:28): [True: 8, False: 12]
  Branch (94:43): [True: 6, False: 6]
  Branch (94:68): [True: 1, False: 5]
95
20
            destination -= width;
96
20
            memcpy(data + destination, a[0]->as.bytes.data + source, width);
97
20
            source += width;
98
20
        }
99
6
        Value *v = value_data(V_STR, data, n);
100
6
        free(data);
101
6
        return v;
102
6
    }
103
104
723k
    if (!strcmp(name, "is_digit") || !strcmp(name, "is_letter") || !strcmp(name, "is_whitespace")) {
  Branch (104:9): [True: 109k, False: 613k]
  Branch (104:38): [True: 252k, False: 360k]
  Branch (104:68): [True: 158k, False: 202k]
105
520k
        REQUIRE(a[0]->kind == V_STR, "VM trap: character class requires Str");
106
520k
        uint8_t c = a[0]->as.bytes.length == 1 ? a[0]->as.bytes.data[0] : 0;
  Branch (106:21): [True: 520k, False: 1]
107
520k
        bool result = !strcmp(name, "is_digit") ? c >= '0' && c <= '9'
  Branch (107:23): [True: 109k, False: 411k]
  Branch (107:51): [True: 64.2k, False: 45.2k]
  Branch (107:63): [True: 12.4k, False: 51.7k]
108
520k
                      : !strcmp(name, "is_letter")
  Branch (108:25): [True: 252k, False: 158k]
109
411k
                          ? (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
  Branch (109:30): [True: 201k, False: 51.3k]
  Branch (109:42): [True: 11.8k, False: 189k]
  Branch (109:56): [True: 177k, False: 63.9k]
  Branch (109:68): [True: 171k, False: 5.78k]
110
411k
                          : c == ' ' || c == '\t' || c == '\r' || c == '\n';
  Branch (110:29): [True: 55.8k, False: 102k]
  Branch (110:41): [True: 0, False: 102k]
  Branch (110:54): [True: 0, False: 102k]
  Branch (110:67): [True: 173, False: 102k]
111
520k
        return value_bool(result);
112
520k
    }
113
114
202k
    if (!strcmp(name, "byte_append")) {
  Branch (114:9): [True: 97.9k, False: 104k]
115
97.9k
        REQUIRE(a[0]->kind == V_BYTES && a[1]->kind == V_NAT,
116
97.9k
                "VM trap: byte_append requires Bytes and Nat");
117
97.9k
        size_t byte;
118
97.9k
        if (!value_index(a[1], &byte) || byte > 255) {
  Branch (118:13): [True: 0, False: 97.9k]
  Branch (118:42): [True: 2, False: 97.9k]
119
2
            vm->error = "VM trap: byte value is outside 0..255";
120
2
            return NULL;
121
2
        }
122
97.9k
        size_t n = a[0]->as.bytes.length;
123
97.9k
        uint8_t *data = malloc(n + 1);
124
97.9k
        if (!data) {
  Branch (124:13): [True: 0, False: 97.9k]
125
0
            return NULL;
126
0
        }
127
97.9k
        memcpy(data, a[0]->as.bytes.data, n);
128
97.9k
        data[n] = (uint8_t)byte;
129
97.9k
        Value *v = value_data(V_BYTES, data, n + 1);
130
97.9k
        free(data);
131
97.9k
        return v;
132
97.9k
    }
133
134
104k
    if (!strcmp(name, "bytes_concat")) {
  Branch (134:9): [True: 62.0k, False: 42.3k]
135
62.0k
        REQUIRE(a[0]->kind == V_BYTES && a[1]->kind == V_BYTES,
136
62.0k
                "VM trap: bytes_concat requires Bytes");
137
62.0k
        size_t n = a[0]->as.bytes.length, m = a[1]->as.bytes.length;
138
62.0k
        uint8_t *data = malloc(n + m);
139
62.0k
        if (!data && n + m) {
  Branch (139:13): [True: 0, False: 62.0k]
  Branch (139:22): [True: 0, False: 0]
140
0
            return NULL;
141
0
        }
142
62.0k
        memcpy(data, a[0]->as.bytes.data, n);
143
62.0k
        memcpy(data + n, a[1]->as.bytes.data, m);
144
62.0k
        Value *v = value_data(V_BYTES, data, n + m);
145
62.0k
        free(data);
146
62.0k
        return v;
147
62.0k
    }
148
149
42.3k
    if (!strcmp(name, "byte_len")) {
  Branch (149:9): [True: 18.8k, False: 23.4k]
150
18.8k
        REQUIRE(a[0]->kind == V_BYTES, "VM trap: byte_len requires Bytes");
151
18.8k
        return value_size(a[0]->as.bytes.length);
152
18.8k
    }
153
154
23.4k
    if (!strcmp(name, "byte_get")) {
  Branch (154:9): [True: 5.55k, False: 17.9k]
155
5.55k
        REQUIRE(a[0]->kind == V_BYTES && a[1]->kind == V_NAT,
156
5.55k
                "VM trap: byte_get requires Bytes and Nat");
157
5.55k
        size_t i;
158
5.55k
        if (!value_index(a[1], &i) || i >= a[0]->as.bytes.length) {
  Branch (158:13): [True: 0, False: 5.55k]
  Branch (158:39): [True: 0, False: 5.55k]
159
0
            vm->error = "VM trap: byte index is out of bounds";
160
0
            return NULL;
161
0
        }
162
5.55k
        return value_size(a[0]->as.bytes.data[i]);
163
5.55k
    }
164
165
17.9k
    if (!strcmp(name, "utf8_encode")) {
  Branch (165:9): [True: 14.3k, False: 3.55k]
166
14.3k
        REQUIRE(a[0]->kind == V_STR, "VM trap: utf8_encode requires Str");
167
14.3k
        return value_data(V_BYTES, a[0]->as.bytes.data, a[0]->as.bytes.length);
168
14.3k
    }
169
170
3.55k
    if (!strcmp(name, "utf8_decode")) {
  Branch (170:9): [True: 550, False: 3.00k]
171
550
        REQUIRE(a[0]->kind == V_BYTES, "VM trap: utf8_decode requires Bytes");
172
549
        if (!utf8(a[0]->as.bytes.data, a[0]->as.bytes.length)) {
  Branch (172:13): [True: 2, False: 547]
173
2
            vm->error = "VM trap: invalid UTF-8";
174
2
            return NULL;
175
2
        }
176
547
        return value_data(V_STR, a[0]->as.bytes.data, a[0]->as.bytes.length);
177
549
    }
178
179
3.00k
    if (!strcmp(name, "nat_from_str")) {
  Branch (179:9): [True: 3.00k, False: 0]
180
3.00k
        REQUIRE(a[0]->kind == V_STR, "VM trap: nat_from_str requires Str");
181
3.00k
        if (!a[0]->as.bytes.length) {
  Branch (181:13): [True: 0, False: 3.00k]
182
0
            vm->error = "VM trap: text is not a Nat";
183
0
            return NULL;
184
0
        }
185
13.8k
        for (size_t i = 0; i < a[0]->as.bytes.length; i++) {
  Branch (185:28): [True: 10.8k, False: 3.00k]
186
10.8k
            if (a[0]->as.bytes.data[i] < '0' || a[0]->as.bytes.data[i] > '9') {
  Branch (186:17): [True: 0, False: 10.8k]
  Branch (186:49): [True: 0, False: 10.8k]
187
0
                vm->error = "VM trap: text is not a Nat";
188
0
                return NULL;
189
0
            }
190
10.8k
        }
191
3.00k
        PnBigInt n;
192
3.00k
        if (!pn_big_from_digits(&n, (char *)a[0]->as.bytes.data, a[0]->as.bytes.length, 1)) {
  Branch (192:13): [True: 0, False: 3.00k]
193
0
            return NULL;
194
0
        }
195
3.00k
        Value *v = value_big(V_NAT, &n);
196
3.00k
        pn_big_free(&n);
197
3.00k
        return v;
198
3.00k
    }
199
0
    vm->error = "VM trap: unknown builtin";
200
0
    return NULL;
201
3.00k
}