Here are 3 more possibilities that use comptime:
- creates arrays and then concatenates those with
++
- takes a tuple of 2 element-tuples which describe ranges and then creates one long tuple from those at comptime
- combines 1. and 2. for a simpler use syntax
I think 1. is better then 2. because it is simpler and doesn’t require manually specifying the length. I wasn’t able to get 2. to a point where it is able to infer the length.
The downside of 1. is that you need to specify the type of the target array (it would be nice if we could write comptime functions and get access to the inferred return type, similar to what builtins do), but I think the downside isn’t that big.
Because 2. is sadly not able to infer the length (Is there a way to get 2. to infer the size?) of the comptime generated tuple, it isn’t really helpful, so I created 3. which is a mix of 1. and 2.
const std = @import("std");
fn literalRange(comptime T: type, comptime start: comptime_int, comptime end: comptime_int) [end - start + 1]T {
var res: [end - start + 1]T = undefined;
for (start..end + 1, 0..) |element, i| {
res[i] = @intCast(element);
}
return res;
}
fn rangesLength(comptime ranges: anytype) comptime_int {
var length = 0;
for (ranges) |range| {
const start = range[0];
const end = range[1];
length += end - start + 1;
}
return length;
}
fn TupleRanges(comptime ranges: anytype) type {
const types = [1]type{comptime_int} ** rangesLength(ranges);
return std.meta.Tuple(&types);
}
fn tupleRanges(comptime ranges: anytype) TupleRanges(ranges) {
var res: TupleRanges(ranges) = undefined;
var next = 0;
inline for (ranges) |range| {
const current = next;
const start = range[0];
const end = range[1];
inline for (start..end + 1, current..) |element, i| {
res[i] = element;
next += 1;
}
}
return res;
}
fn LiteralRanges(comptime T: type, comptime ranges: anytype) type {
return [rangesLength(ranges)]T;
}
fn literalRanges(comptime T: type, comptime ranges: anytype) LiteralRanges(T, ranges) {
comptime var res: LiteralRanges(T, ranges) = undefined;
comptime var next = 0;
inline for (ranges) |range| {
const current = next;
const start = range[0];
const end = range[1];
inline for (start..end + 1, current..) |element, i| {
res[i] = element;
next += 1;
}
}
return res;
}
pub fn main() !void {
const default_word_characters = init: {
const num_letters = 'z' - 'a' + 1;
var word_characters: [num_letters * 2]u8 = undefined;
for ('A'..'Z' + 1, 0..) |char, idx| {
word_characters[idx] = @intCast(char);
}
for ('a'..'z' + 1, num_letters..) |char, idx| {
word_characters[idx] = @intCast(char);
}
break :init word_characters;
};
for (default_word_characters) |char| {
std.debug.print("{c}\n", .{char});
}
std.debug.print("\n-------------------------\n", .{});
// 1.
const default_word_characters2 = literalRange(u8, 'A', 'Z') ++ literalRange(u8, 'a', 'z');
for (default_word_characters2) |char| {
std.debug.print("{c}\n", .{char});
}
std.debug.print("\n-------------------------\n", .{});
// 2.
const tuple = tupleRanges(.{ .{ 'A', 'Z' }, .{ 'a', 'z' } });
const default_word_characters3: [tuple.len]u8 = tuple;
for (default_word_characters3) |char| {
std.debug.print("{c}\n", .{char});
}
std.debug.print("\n-------------------------\n", .{});
// 3.
const default_word_characters4 = literalRanges(u8, .{ .{ 'A', 'Z' }, .{ 'a', 'z' } });
for (default_word_characters4) |char| {
std.debug.print("{c}\n", .{char});
}
}