1const std = @import("std");
2const extras = @import("extras");
3
4const base = 36;
5const tmin = 1;
6const tmax = 26;
7const skew = 38;
8const damp = 700;
9const initial_bias = 72;
10const initial_n = 128;
11const delimiter = '-';
12
13pub fn decode(map: *extras.ManyArrayList(u8), map_n: usize) !void {
14 var input = map.items(map_n);
15 var offset: usize = 0;
16 const initial_len = input.len;
17 // std.debug.assert(std.mem.startsWith(u8, map.items(n), "xn--"));
18 offset += 4;
19 input = input[offset..initial_len];
20
21 // let n = initial_n
22 var n: u21 = 128;
23 // let i = 0
24 var i: u32 = 0;
25 // let bias = initial_bias
26 var bias: u32 = initial_bias;
27 // let output = an empty string indexed from 0
28 // output is ascii so output_length as codepoint or bytes is the same
29 var output_length: u32 = 0;
30 // consume all code points before the last delimiter (if there is one) and copy them to output, fail on any non-basic code point
31 // if more than zero code points were consumed then consume one more (which will be the last delimiter)
32 if (std.mem.lastIndexOfScalar(u8, input, delimiter)) |idx| {
33 // we're passing input as what we're appending so we have to ensure the backing allocation is not invalidated by calling append
34 try map.list.ensureUnusedCapacity(map.allocator, idx);
35 input = map.items(map_n)[offset..initial_len];
36 try map.appendSlice(map_n, input[0..idx]);
37 offset += idx + 1;
38 input = input[idx + 1 ..];
39 output_length += @intCast(idx);
40 }
41 // while the input is not exhausted do begin
42 while (offset < initial_len) {
43 // let oldi = i
44 const oldi = i;
45 // let w = 1
46 var w: u32 = 1;
47 // for k = base to infinity in steps of base do begin
48 var k: u32 = base;
49 while (true) : (k += base) {
50 // consume a code point, or fail if there was none to consume
51 if (input.len == 0) return error.InvalidPunycode;
52 const sl = input[0 .. std.unicode.utf8ByteSequenceLength(input[0]) catch unreachable];
53 offset += sl.len;
54 input = input[sl.len..];
55 // let digit = the code point's digit-value, fail if it has none
56 const cp = std.unicode.utf8Decode(sl) catch unreachable;
57 const digit: u32 = blk: {
58 if (cp >= 'A' and cp <= 'Z') break :blk cp - 'A';
59 if (cp >= 'a' and cp <= 'z') break :blk cp - 'a';
60 if (cp >= '0' and cp <= '9') break :blk cp - '0' + 26;
61 return error.InvalidPunycode;
62 };
63 // let i = i + digit * w, fail on overflow
64 i = std.math.add(u32, i, std.math.mul(u32, digit, w) catch return error.InvalidPunycode) catch return error.InvalidPunycode;
65 // let t = tmin if k <= bias {+ tmin}, or
66 // tmax if k >= bias + tmax, or k - bias otherwise
67 const t = if (k <= bias) tmin else if (k >= bias + tmax) tmax else k - bias;
68 // if digit < t then break
69 if (digit < t) break;
70 // let w = w * (base - t), fail on overflow
71 w = w * (base - t);
72 }
73 // let bias = adapt(i - oldi, length(output) + 1, test oldi is 0?)
74 bias = adapt(i - oldi, output_length + 1, oldi == 0);
75 // let n = n + i div (length(output) + 1), fail on overflow
76 n = std.math.add(u21, n, @intCast(i / (output_length + 1))) catch return error.InvalidPunycode;
77 // let i = i mod (length(output) + 1)
78 i = i % (output_length + 1);
79 // {if n is a basic code point then fail}
80 std.debug.assert(n > std.math.maxInt(u7));
81 // insert n into output at position i
82 if (i == output_length) {
83 var buf: [4]u8 = undefined;
84 const len = std.unicode.utf8Encode(n, &buf) catch unreachable;
85 try map.appendSlice(map_n, buf[0..len]);
86 input = map.items(map_n)[offset..initial_len];
87 output_length += 1;
88 } else {
89 var ioff: usize = 0;
90 var cpi: usize = 0;
91 var it = std.unicode.Utf8View.initUnchecked(map.items(map_n)[initial_len..]).iterator();
92 while (it.nextCodepointSlice()) |sl| {
93 if (cpi == i) break;
94 ioff += sl.len;
95 cpi += 1;
96 }
97 var buf: [4]u8 = undefined;
98 const len = std.unicode.utf8Encode(n, &buf) catch unreachable;
99 try map.replace(map_n, initial_len + ioff, 0, buf[0..len]);
100 input = map.items(map_n)[offset..initial_len];
101 output_length += 1;
102 }
103 // increment i
104 i += 1;
105 }
106
107 try map.replace(map_n, 0, initial_len, "");
108}
109
110fn adapt(delta_: u32, numpoints: u32, firsttime: bool) u32 {
111 var delta = delta_;
112 // if firsttime then let delta = delta div damp
113 // else let delta = delta div 2
114 delta = if (firsttime) delta / damp else delta / 2;
115 // let delta = delta + (delta div numpoints)
116 delta = delta + (delta / numpoints);
117 // let k = 0
118 var k: u32 = 0;
119 // while delta > ((base - tmin) * tmax) div 2 do begin
120 while (delta > ((base - tmin) * tmax) / 2) {
121 // let delta = delta div (base - tmin)
122 delta /= base - tmin;
123 // let k = k + base
124 k += base;
125 }
126 // return k + (((base - tmin + 1) * delta) div (delta + skew))
127 return k + (((base - tmin + 1) * delta) / (delta + skew));
128}
129
130pub fn encode(map: *extras.ManyArrayList(u8), map_n: usize) !void {
131 const initial_len = map.lengths.items[map_n];
132 var input = map.items(map_n)[0..initial_len];
133 if (extras.matchesAll(u8, input, std.ascii.isAscii)) return;
134 try map.appendSlice(map_n, "xn--");
135 input = map.items(map_n)[0..initial_len];
136 const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
137
138 var input_len_in_cp: u32 = 0;
139 var it = std.unicode.Utf8View.initUnchecked(input).iterator();
140 while (it.nextCodepointSlice()) |_| input_len_in_cp += 1;
141 it.i = 0;
142
143 // let n = initial_n
144 var n: u21 = initial_n;
145 // let delta = 0
146 var delta: u32 = 0;
147 // let bias = initial_bias
148 var bias: u32 = initial_bias;
149 // let h = b = the number of basic code points in the input
150 // copy them to the output in order, followed by a delimiter if b > 0
151 var h: u32 = 0;
152 var b: u32 = 0;
153 {
154 var i: usize = 0;
155 while (i < input.len) : (i += 1) {
156 if (std.ascii.isAscii(input[i])) b += 1;
157 }
158 try map.list.ensureUnusedCapacity(map.allocator, b + 1);
159 input = map.items(map_n)[0..initial_len];
160 it.bytes = input;
161 i = 0;
162 while (i < input.len) : (i += 1) {
163 if (std.ascii.isAscii(input[i])) map.appendSlice(map_n, &.{input[i]}) catch unreachable;
164 }
165 if (b > 0) map.appendSlice(map_n, &.{delimiter}) catch unreachable;
166 h = b;
167 }
168 // {if the input contains a non-basic code point < n then fail}
169 // while h < length(input) do begin
170 while (h < input_len_in_cp) {
171 // let m = the minimum {non-basic} code point >= n in the input
172 const m = get_m(it.bytes, n);
173 // let delta = delta + (m - n) * (h + 1), fail on overflow
174 delta = delta + (m - n) * (h + 1);
175 if (false) return error.InvalidPunycode;
176 // let n = m
177 n = m;
178 // for each code point c in the input (in order) do begin
179 it.i = 0;
180 while (it.nextCodepointSlice()) |sl| {
181 const c = std.unicode.utf8Decode(sl) catch unreachable;
182 // if c < n {or c is basic} then increment delta, fail on overflow
183 if (c < n) {
184 delta = std.math.add(u32, delta, 1) catch return error.InvalidPunycode;
185 }
186 // if c == n then begin
187 if (c == n) {
188 // let q = delta
189 var q = delta;
190 // for k = base to infinity in steps of base do begin
191 var k: u23 = base;
192 while (true) : (k += base) {
193 // let t = tmin if k <= bias {+ tmin}, or
194 // tmax if k >= bias + tmax, or k - bias otherwise
195 const t = if (k <= bias) tmin else if (k >= bias + tmax) tmax else k - bias;
196 // if q < t then break
197 if (q < t) break;
198 // output the code point for digit t + ((q - t) mod (base - t))
199 try map.appendSlice(map_n, &.{alphabet[t + ((q - t) % (base - t))]});
200 it.bytes = map.items(map_n)[0..initial_len];
201 // let q = (q - t) div (base - t)
202 q = (q - t) / (base - t);
203 }
204 // output the code point for digit q
205 try map.appendSlice(map_n, &.{alphabet[q]});
206 it.bytes = map.items(map_n)[0..initial_len];
207 // let bias = adapt(delta, h + 1, test h equals b?)
208 bias = adapt(delta, h + 1, h == b);
209 // let delta = 0
210 delta = 0;
211 // increment h
212 h += 1;
213 }
214 }
215 // increment delta and n
216 delta += 1;
217 n += 1;
218 }
219 // clear input from map_n item
220 map.replace(map_n, 0, initial_len, "") catch unreachable;
221}
222
223fn get_m(input: []const u8, n: u21) u21 {
224 var min: u21 = std.math.maxInt(u21);
225 var it = std.unicode.Utf8View.initUnchecked(input).iterator();
226 while (it.nextCodepointSlice()) |sl| {
227 const cp = std.unicode.utf8Decode(sl) catch unreachable;
228 if (cp < n) continue;
229 min = @min(min, cp);
230 }
231 return min;
232}