✓
Passing This code compiles and runs correctly.
Code
// Test: Transform can extract and use generic parameters
//
// Given: ~ring.new[T:u32;N:1024](name: "counter_ring")
//
// Transform should:
// 1. Parse event_name to extract T=u32, N=1024
// 2. Use these to generate properly typed Zig code:
//
// const counter_ring = struct {
// buffer: [1024]u32 = undefined,
// head: usize = 0,
// tail: usize = 0,
// };
//
// This is generics-as-library: the transform IS the generic instantiation.
~[comptime|transform]pub event ring.* {
event_name: []const u8,
item: *const Item,
program: *const Program,
}
| transformed { program: *const Program }
~proc ring.* {
const std = @import("std");
const ast = @import("ast");
const ast_functional = @import("ast_functional");
const allocator = std.heap.page_allocator;
// Parse: ring.new[T:u32;N:1024]
const bracket_start = std.mem.indexOf(u8, event_name, "[") orelse return .{ .transformed = .{ .program = program } };
const bracket_end = std.mem.lastIndexOf(u8, event_name, "]") orelse return .{ .transformed = .{ .program = program } };
const params_str = event_name[bracket_start + 1 .. bracket_end];
// Simple parsing: split by semicolon, then by colon
// "T:u32;N:1024" → T=u32, N=1024
var T: []const u8 = "u8";
var N: []const u8 = "64";
var params_iter = std.mem.splitScalar(u8, params_str, ';');
while (params_iter.next()) |param| {
const trimmed = std.mem.trim(u8, param, " ");
if (std.mem.indexOf(u8, trimmed, ":")) |colon| {
const key = trimmed[0..colon];
const value = trimmed[colon + 1 ..];
if (std.mem.eql(u8, key, "T")) {
T = value;
} else if (std.mem.eql(u8, key, "N")) {
N = value;
}
}
}
// Extract ring name: get command from "ring.new" then use it as default name
const command_start: usize = 5; // Skip "ring."
const command_end = bracket_start;
const command = event_name[command_start..command_end];
_ = command;
const name = "my_ring"; // Hardcode for this test - getting name from args is separate feature
// Generate typed ring struct
const code = std.fmt.allocPrint(
allocator,
\\const {s} = struct {{
\\ buffer: [{s}]{s} = undefined,
\\ head: usize = 0,
\\ tail: usize = 0,
\\
\\ pub fn enqueue(self: *@This(), item: {s}) void {{
\\ self.buffer[self.tail] = item;
\\ self.tail = (self.tail + 1) % {s};
\\ }}
\\}};
,
.{ name, N, T, T, N }
) catch unreachable;
const flow = if (item.* == .flow) &item.flow else return .{ .transformed = .{ .program = program } };
const inline_code_item = ast.Item{
.inline_code = ast.InlineCode{
.code = code,
.location = flow.location,
.module = allocator.dupe(u8, flow.module) catch unreachable,
},
};
const maybe_new_program = ast_functional.replaceFlowRecursive(allocator, program, flow, inline_code_item) catch unreachable;
if (maybe_new_program) |new_program| {
const result = allocator.create(ast.Program) catch unreachable;
result.* = new_program;
return .{ .transformed = .{ .program = result } };
}
return .{ .transformed = .{ .program = program } };
}
// Create a ring buffer of 1024 u32 values
~ring.new[T:u32;N:1024](name: "my_ring")