@@ 224,15 224,107 @@ end
--
-- Shapes that fall through to encode_field (no writer set):
-- - map fields (need `pairs()` over user data; can't be helped)
--- - repeated fields (TODO — common shape, worth specializing)
-- - fields inside a oneof (active-branch dispatch happens in encode_message)
-- ---------------------------------------------------------------------------
+local function build_repeated_writer(f)
+ local fname = f.name
+ local kind = f.kind
+
+ if kind == 'scalar' then
+ local handler = scalar[f.proto_type]
+ if not handler then return nil end
+ local encode_value = handler.encode
+
+ if f.packed and handler.packable then
+ local tag_bytes = wire.encode_tag(f.id, wire.WIRE_LEN)
+ return function(data, out)
+ local v = data[fname]
+ if v == nil then return end
+ local nv = #v
+ if nv == 0 then return end
+ local parts = {}
+ for i = 1, nv do parts[i] = encode_value(v[i]) end
+ local payload = table.concat(parts)
+ local n = #out
+ out[n + 1] = tag_bytes
+ out[n + 2] = wire.encode_len(payload)
+ end
+ end
+
+ -- Unpacked repeated scalar (also covers string/bytes — LEN
+ -- wire type, which is non-packable by proto3 rules).
+ local tag_bytes = wire.encode_tag(f.id, handler.wire)
+ return function(data, out)
+ local v = data[fname]
+ if v == nil then return end
+ local nv = #v
+ if nv == 0 then return end
+ local n = #out
+ for i = 1, nv do
+ n = n + 1; out[n] = tag_bytes
+ n = n + 1; out[n] = encode_value(v[i])
+ end
+ end
+ end
+
+ if kind == 'message' then
+ local sub_desc = f.message
+ local tag_bytes = wire.encode_tag(f.id, wire.WIRE_LEN)
+ return function(data, out)
+ local v = data[fname]
+ if v == nil then return end
+ local nv = #v
+ if nv == 0 then return end
+ local n = #out
+ for i = 1, nv do
+ n = n + 1; out[n] = tag_bytes
+ n = n + 1; out[n] = wire.encode_len(encode_msg(sub_desc, v[i]))
+ end
+ end
+ end
+
+ if kind == 'enum' then
+ local enum_desc = f.enum
+ -- proto3 default: repeated enums are packed unless explicitly disabled.
+ if f.packed ~= false then
+ local tag_bytes = wire.encode_tag(f.id, wire.WIRE_LEN)
+ return function(data, out)
+ local v = data[fname]
+ if v == nil then return end
+ local nv = #v
+ if nv == 0 then return end
+ local parts = {}
+ for i = 1, nv do
+ parts[i] = wire.encode_varint(encode_enum_value(enum_desc, v[i]))
+ end
+ local n = #out
+ out[n + 1] = tag_bytes
+ out[n + 2] = wire.encode_len(table.concat(parts))
+ end
+ end
+ local tag_bytes = wire.encode_tag(f.id, wire.WIRE_VARINT)
+ return function(data, out)
+ local v = data[fname]
+ if v == nil then return end
+ local nv = #v
+ if nv == 0 then return end
+ local n = #out
+ for i = 1, nv do
+ n = n + 1; out[n] = tag_bytes
+ n = n + 1; out[n] = wire.encode_varint(encode_enum_value(enum_desc, v[i]))
+ end
+ end
+ end
+
+ return nil
+end
+
local function build_writer(f)
-- Maps and oneof branches keep going through encode_field.
if f.kind == 'map' or f.oneof then return nil end
- -- Repeated fields fall through for now.
- if f.repeated then return nil end
+
+ if f.repeated then return build_repeated_writer(f) end
local fname = f.name
local kind = f.kind