zerodds_lang_rules/emit/go.rs
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! The Go backend of the shared codec walk.
4//!
5//! DDS-XTypes 1.3 §7.4 from the clause, the same as `runtime/c`,
6//! `runtime/python` and the rest do, and none of them calls another.
7//! Where two columns disagree on an octet, one has misread the
8//! specification — which is the only reason to have an eighth.
9//!
10//! # The four decisions this column turns on
11//!
12//! **A member holds its wire width.** `adapter/go.rs` declares `octet`
13//! as `uint8` and `unsigned long long` as `uint64`, so unlike the
14//! Python column there is no width to re-state at the call and unlike
15//! the Java one there is no signedness to work around: a runtime call
16//! takes the member as it is, and the compiler is the range check.
17//!
18//! **A `char` is `uint8` and a `wchar` is `uint16`.** The generated
19//! member holds the code unit, so [`CodecTarget::code_point`] is the
20//! identity and a union over a `wchar` compares its discriminator as a
21//! number — which is what lets a surrogate case label, refused as a
22//! `str` in the Python column, be an ordinary integer here.
23//!
24//! **A `long double` and a `fixed` are decimal text.** Go has no
25//! binary128 and `float64` holds 53 of its 113 significand bits, so
26//! `runtime/go`'s `WriteF128` parses the member's decimal — correctly
27//! rounded, once — and `ReadF128` answers the exact expansion. The
28//! member's type and the codec's agree because both come from
29//! `adapter::go::go_value_type`.
30//!
31//! **An `@optional` member is a pointer, and a pointer needs storage.**
32//! `nil` is the absence, so [`CodecTarget::present`] is a nil test and
33//! [`CodecTarget::set_present`] writes nothing on the way in — the
34//! assignment is what makes it present. The reading direction cannot
35//! assign a `T` to a `*T`, and Go has no address-of on a call's result,
36//! so [`CodecTarget::optional_destination`] marks the place and
37//! [`CodecTarget::read_call`] routes the decoded value through
38//! `zdwire.Ptr` — one statement, no temporary whose name two sites in
39//! one scope would have to share.
40
41use super::{CodecTarget, Constant, Handle, Handoff, Routine, Scalar};
42use crate::adapter::go::{
43 field_name, go_map_entry_name, go_type_name, go_value_type, module_prefix,
44};
45use crate::encode::EncodeError;
46use zerodds_ast::{Ast, CharWidth, MemberNode, TypeKind, TypeNode};
47use zerodds_ast_core::Id;
48use zerodds_lang_cst::common::{Doc, Ident, Literal};
49use zerodds_lang_cst::go;
50
51/// The Go backend.
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct Go;
54
55/// How deep a chain of typedefs and collections is followed.
56const MAX_TYPE_DEPTH: usize = 64;
57
58/// The name the generated file imports the runtime under.
59const RUNTIME: &str = "zdwire";
60
61/// The type name [`CodecTarget::named`] is handed for C's signed
62/// `char`. `emit/encode.rs`'s `map_key_less` writes it where a `map`'s
63/// key is a `char` and the entry order is signed; the Go member is a
64/// `uint8` and the signed reading is `int8`.
65const SIGNED_CHAR: &str = "signed char";
66
67/// An expression, with the one property the walk's seams cannot carry:
68/// whether it is the pointer field an `@optional` member lives behind.
69///
70/// Only [`CodecTarget::optional_destination`] produces the marked form
71/// and only [`CodecTarget::read_call`] and [`CodecTarget::store`] act
72/// on it; every other constructor passes the inner expression through.
73#[derive(Debug, Clone)]
74pub enum GoExpr {
75 /// An ordinary expression.
76 Plain(go::Expr),
77 /// The `*T`-typed field of an `@optional` member, as a place a
78 /// decoded `T` is about to be written into.
79 OptionalSlot(go::Expr),
80}
81
82/// The expression inside, whichever form it took.
83fn inner(expr: GoExpr) -> go::Expr {
84 match expr {
85 GoExpr::Plain(e) | GoExpr::OptionalSlot(e) => e,
86 }
87}
88
89fn plain(e: go::Expr) -> GoExpr {
90 GoExpr::Plain(e)
91}
92
93impl Go {
94 /// A runtime name, qualified: `zdwire.X`.
95 fn runtime_name(name: &str) -> go::Expr {
96 go::Expr::selector(go::Expr::name(RUNTIME), name)
97 }
98
99 /// A runtime operation's Go spelling: `write_u32` is `WriteU32`.
100 /// Each `_`-separated segment keeps its own spelling with the first
101 /// letter raised, so `read_pid_as` is `ReadPidAs` and
102 /// `write_wstring_utf8` is `WriteWstringUtf8` — the names
103 /// `runtime/go` publishes, because that runtime was written against
104 /// this conversion.
105 fn pascal(method: &str) -> String {
106 let mut out = String::new();
107 for segment in method.split('_') {
108 let mut chars = segment.chars();
109 if let Some(first) = chars.next() {
110 out.extend(first.to_uppercase());
111 out.push_str(chars.as_str());
112 }
113 }
114 out
115 }
116
117 /// A string literal.
118 fn text(value: &str) -> GoExpr {
119 plain(go::Expr::Literal(Literal::Str(value.to_string())))
120 }
121
122 /// A runtime constant by its exported name.
123 fn runtime_constant(name: &str) -> GoExpr {
124 plain(Self::runtime_name(name))
125 }
126}
127
128impl CodecTarget for Go {
129 type Expr = GoExpr;
130 type Stmt = go::Stmt;
131 type Block = Vec<go::Stmt>;
132 type Type = go::Type;
133 type Decl = go::Decl;
134
135 // -- types --------------------------------------------------------
136
137 /// The fixed-width Go type each scalar local takes. `Index` is
138 /// `uint32` — a sequence length is a `uint32` on the wire and a Go
139 /// index may be any integer type — and `Size` is `int`, which is
140 /// what `len` and the reader's positions answer in.
141 fn scalar(kind: Scalar) -> Self::Type {
142 match kind {
143 Scalar::Bool => go::Type::Primitive(go::Primitive::Bool),
144 Scalar::Char => go::Type::Primitive(go::Primitive::UInt8),
145 Scalar::Int8 => go::Type::Primitive(go::Primitive::Int8),
146 Scalar::Int16 => go::Type::Primitive(go::Primitive::Int16),
147 Scalar::Int32 => go::Type::Primitive(go::Primitive::Int32),
148 Scalar::Int64 => go::Type::Primitive(go::Primitive::Int64),
149 Scalar::UInt8 => go::Type::Primitive(go::Primitive::UInt8),
150 Scalar::UInt16 => go::Type::Primitive(go::Primitive::UInt16),
151 Scalar::UInt32 | Scalar::Index => go::Type::Primitive(go::Primitive::UInt32),
152 Scalar::UInt64 => go::Type::Primitive(go::Primitive::UInt64),
153 Scalar::Float => go::Type::Primitive(go::Primitive::Float32),
154 Scalar::Double => go::Type::Primitive(go::Primitive::Float64),
155 // The decimal text; the module documentation says why.
156 Scalar::LongDouble => go::Type::Primitive(go::Primitive::String),
157 // `int` deliberately: a stream position is what `len`
158 // answers, and the CST's own Primitive set excludes the
159 // implementation-sized integers because a *declared member*
160 // must not change width between targets — a codec local is
161 // not a member.
162 Scalar::Size => go::Type::named("int"),
163 }
164 }
165
166 fn named(name: &str) -> Self::Type {
167 go::Type::named(name)
168 }
169
170 /// No Go form, and never rendered: a Go function with no results
171 /// has an empty result list, and [`Go::routine`] writes one.
172 fn nothing() -> Self::Type {
173 go::Type::named("")
174 }
175
176 /// The runtime's handles, by their exported names.
177 fn handle(kind: Handle) -> Self::Type {
178 go::Type::named(match kind {
179 Handle::DHeader => "zdwire.DHeader",
180 Handle::EmHeader => "zdwire.EmHeader",
181 Handle::Pid => "zdwire.Pid",
182 Handle::TryConstruct => "zdwire.TryConstruct",
183 Handle::ReaderMark => "zdwire.ReaderMark",
184 Handle::PidRead => "zdwire.PidRead",
185 Handle::EmHeaderRead => "zdwire.EmHeaderRead",
186 })
187 }
188
189 /// The member already is the code unit — `uint8` for a `char`,
190 /// `uint16` for a `wchar` — so there is nothing to convert.
191 fn code_point(value: Self::Expr, _narrow: bool) -> Self::Expr {
192 value
193 }
194
195 /// The runtime's policy values. Strings and integers where
196 /// `runtime/go` takes them so — the spellings are
197 /// `WstringLengthPolicy::as_str` and its siblings in
198 /// `crates/ast-layout/src/key.rs` — and exported constants for the
199 /// enumerated kinds. One arm per constant and no catch-all.
200 fn constant(which: Constant) -> Self::Expr {
201 match which {
202 Constant::WcharWidth2 => plain(go::Expr::int(2)),
203 Constant::WcharWidth4 => plain(go::Expr::int(4)),
204 Constant::WcharFixed => Self::text("fixed"),
205 Constant::WcharGiopLengthPrefixed => Self::text("giop-length-prefixed"),
206 Constant::WstringUnitsStream => Self::text("stream"),
207 Constant::WstringUnitsBigEndianAlways => Self::text("big-endian-always"),
208 Constant::WstringUnitsByteOrderMark => Self::text("byte-order-mark"),
209 Constant::WstringUnitsHostUnswapped => Self::text("host-unswapped"),
210 Constant::WstringSpecification => Self::text("specification"),
211 Constant::WstringCharacterCount => Self::text("character-count"),
212 Constant::WstringCharacterCountTerminated => Self::text("character-count-terminated"),
213 Constant::WstringSpecificationWideUnits => Self::text("specification-wide-units"),
214 Constant::WstringRosTypeSupport => Self::text("ros-type-support"),
215 Constant::WstringOctetsTerminated2 => Self::text("octets/terminated/2"),
216 Constant::WstringOctetsTerminated4 => Self::text("octets/terminated/4"),
217 Constant::WstringCharactersTerminated2 => Self::text("characters/terminated/2"),
218 Constant::FixedDeclared => Self::text("declared"),
219 Constant::FixedValuePrecision => Self::text("value-precision"),
220 Constant::FixedValueStringDigits => Self::text("value-string-digits"),
221 Constant::ParameterMember => Self::runtime_constant("ParameterMember"),
222 Constant::ParameterOptional => Self::runtime_constant("ParameterOptional"),
223 Constant::TryConstructDiscard => Self::runtime_constant("TryConstructDiscard"),
224 Constant::TryConstructTrim => Self::runtime_constant("TryConstructTrim"),
225 Constant::TryConstructUseDefault => Self::runtime_constant("TryConstructUseDefault"),
226 Constant::StatusOk => Self::runtime_constant("StatusOk"),
227 Constant::StatusDiscarded => Self::runtime_constant("StatusDiscarded"),
228 Constant::StatusImpossibleLength => Self::runtime_constant("StatusImpossibleLength"),
229 Constant::StatusOutOfRange => Self::runtime_constant("StatusOutOfRange"),
230 Constant::StatusTruncated => Self::runtime_constant("StatusTruncated"),
231 }
232 }
233
234 /// The unsigned type wide enough for `bits` of a bit set — the same
235 /// answer the materialiser's `go_holder` gives its fields.
236 fn unsigned_holder(bits: u16) -> Self::Type {
237 go::Type::Primitive(crate::adapter::go::go_holder(bits))
238 }
239
240 fn value_type(ast: &Ast, id: Id<TypeNode>) -> Self::Type {
241 // Written as the match `unwrap_or` folds away, so the fallback
242 // carries its reason where the silent-drop guard reads it: a
243 // type with no Go form was refused by the materialiser before a
244 // codec walk ran, and `any` here makes the impossible fail in
245 // `go build`, loudly, rather than compile into a plausible
246 // member.
247 #[allow(clippy::manual_unwrap_or_default, clippy::manual_unwrap_or)]
248 match go_value_type(ast, id) {
249 Ok(type_) => type_,
250 Err(_) => go::Type::Primitive(go::Primitive::Any),
251 }
252 }
253
254 fn type_name(ast: &Ast, id: Id<TypeNode>) -> String {
255 type_tag(ast, id, 0)
256 }
257
258 /// A name the entry declaration and the codec both reach, and a
259 /// distinct one per type when the tag walk refuses: the slot keeps
260 /// two refused entries from collapsing onto one declaration, which
261 /// would be a silent merge of two map types.
262 fn map_entry_name(ast: &Ast, id: Id<TypeNode>) -> String {
263 go_map_entry_name(ast, id).unwrap_or_else(|_| format!("ZdUnresolvedEntry{}", id.slot()))
264 }
265
266 /// The flattened, exported module path — `Common_Util_` — which is
267 /// the same prefix the materialiser puts on a package-scope
268 /// constant, taken from the same function.
269 fn module_prefix(ast: &Ast, id: Id<TypeNode>) -> String {
270 module_prefix(ast, id)
271 }
272
273 /// Escaped first and exported second, exactly as the materialiser
274 /// spells a field: the codec has to reach the member the
275 /// declaration wrote.
276 fn escape(name: &str) -> String {
277 field_name(name)
278 }
279
280 /// The base is the first member `parent` (XTypes 1.3 §7.5.1.4.1.3).
281 fn inherited_path(depth: usize) -> Option<String> {
282 Some(format!("{}.", field_name("parent")).repeat(depth))
283 }
284
285 // -- expressions ---------------------------------------------------
286
287 fn name(text: &str) -> Self::Expr {
288 plain(go::Expr::name(text))
289 }
290
291 fn int(value: i128) -> Self::Expr {
292 plain(go::Expr::int(value))
293 }
294
295 /// The raw tokens the walk escapes through [`Literal::Int`],
296 /// rewritten where Go spells them differently — which for `true`
297 /// and `false` it does not, but the tree's `Bool` variant is what
298 /// renders them and an `Int` carrying a keyword would be pasted as
299 /// digits are.
300 fn literal(value: Literal) -> Self::Expr {
301 let Literal::Int(text) = &value else {
302 return plain(go::Expr::Literal(value));
303 };
304 match text.as_str() {
305 "true" => plain(go::Expr::Literal(Literal::Bool(true))),
306 "false" => plain(go::Expr::Literal(Literal::Bool(false))),
307 _ => plain(go::Expr::Literal(value)),
308 }
309 }
310
311 /// The number, untyped. A Go untyped constant converts to the
312 /// operand type it meets, so a union label needs no suffix; the
313 /// conversion the walk wraps around it supplies the type where one
314 /// is needed.
315 fn wide_int(value: i128) -> Self::Expr {
316 plain(go::Expr::int(value))
317 }
318
319 /// The number, with no `u` suffix: `5u` is not a Go token, and an
320 /// untyped constant already converts against a `uint32` counter.
321 fn unsigned_int(value: i128) -> Self::Expr {
322 plain(go::Expr::int(value))
323 }
324
325 /// `uint64(..)`, typed: `^` on an untyped constant is a negative
326 /// constant that does not convert to `uint64`, and a mask the walk
327 /// complements has to already be one.
328 fn wide_unsigned(value: u64) -> Self::Expr {
329 plain(go::Expr::Convert {
330 type_: Box::new(go::Type::Primitive(go::Primitive::UInt64)),
331 value: Box::new(go::Expr::int(i128::from(value))),
332 })
333 }
334
335 /// `int64(..)`, typed, for the enumerator patterns `zd_raw` is
336 /// compared against.
337 fn wide_signed(value: i128) -> Self::Expr {
338 plain(go::Expr::Convert {
339 type_: Box::new(go::Type::Primitive(go::Primitive::Int64)),
340 value: Box::new(go::Expr::int(value)),
341 })
342 }
343
344 /// `v.X` — and `v.D` for the discriminator, whose `_d` no column
345 /// shares: Go cannot export a name beginning with an underscore,
346 /// and `adapter/go.rs` declares it `D`.
347 fn member(base: &str, field: &str) -> Self::Expr {
348 let spelled = if field == "_d" { "D" } else { field };
349 plain(go::Expr::selector(go::Expr::name(base), spelled))
350 }
351
352 fn whole(base: &str) -> Self::Expr {
353 plain(go::Expr::name(base))
354 }
355
356 fn field_of(value: Self::Expr, field: &str) -> Self::Expr {
357 plain(go::Expr::selector(inner(value), field))
358 }
359
360 /// A header's field, in the exported spelling `runtime/go`
361 /// publishes: `member_id` is `MemberId`, `must_understand` is
362 /// `MustUnderstand`.
363 fn header_field(value: Self::Expr, field: &str) -> Self::Expr {
364 plain(go::Expr::selector(inner(value), &Self::pascal(field)))
365 }
366
367 fn index(value: Self::Expr, at: Self::Expr) -> Self::Expr {
368 plain(go::Expr::Index {
369 value: Box::new(inner(value)),
370 index: Box::new(inner(at)),
371 })
372 }
373
374 /// `uint32(len(v))`, or the bound where the materialiser fixed one.
375 /// The conversion is the crossing written out: `len` answers an
376 /// `int`, a sequence's count is a `uint32` on the wire, and Go has
377 /// no implicit conversion between the two.
378 fn count_of(base: &str, fixed: Option<u64>) -> Self::Expr {
379 match fixed {
380 Some(bound) => plain(go::Expr::int(i128::from(bound))),
381 None => plain(go::Expr::Convert {
382 type_: Box::new(go::Type::Primitive(go::Primitive::UInt32)),
383 value: Box::new(go::Expr::call(
384 go::Expr::name("len"),
385 vec![go::Expr::name(base)],
386 )),
387 }),
388 }
389 }
390
391 /// The slice itself: a Go slice is the elements.
392 fn elements(base: &str) -> Self::Expr {
393 plain(go::Expr::name(base))
394 }
395
396 /// `v.X` — the case fields sit directly on the struct, beside `D`.
397 fn case_member(base: &str, field: &str) -> Self::Expr {
398 Self::member(base, field)
399 }
400
401 /// `entry.Key` and `entry.Value`, the two fields the materialiser
402 /// gives a map's entry struct.
403 fn entry_half(entry: Self::Expr, key: bool) -> Self::Expr {
404 plain(go::Expr::selector(
405 inner(entry),
406 if key { "Key" } else { "Value" },
407 ))
408 }
409
410 fn absent() -> Self::Expr {
411 plain(go::Expr::Literal(Literal::Null))
412 }
413
414 /// Nothing: a Go string and a Go slice own their storage, and there
415 /// is no allocation failure for the guard to test.
416 fn materialised(_value: Self::Expr) -> Option<Self::Expr> {
417 None
418 }
419
420 /// `*v.X` — the pointer dereferenced, because the member's codec
421 /// takes the value. Only ever reached inside a branch the presence
422 /// test guarded.
423 fn optional_value(_ast: &Ast, _id: Id<TypeNode>, base: &str, field: &str) -> Self::Expr {
424 plain(go::Expr::Unary {
425 op: "*",
426 value: Box::new(inner(Self::member(base, field))),
427 })
428 }
429
430 /// `v.X != nil`.
431 fn present(base: &str, field: &str) -> Self::Expr {
432 plain(go::Expr::Binary {
433 op: "!=",
434 lhs: Box::new(inner(Self::member(base, field))),
435 rhs: Box::new(go::Expr::Literal(Literal::Null)),
436 })
437 }
438
439 /// Nothing on the way in — the assignment through `zdwire.Ptr` is
440 /// what makes the member present — and `v.X = nil` on the way out.
441 fn set_present(base: &str, field: &str, present: bool) -> Vec<Self::Stmt> {
442 if present {
443 return Vec::new();
444 }
445 vec![go::Stmt::Assign {
446 place: inner(Self::member(base, field)),
447 value: go::Expr::Literal(Literal::Null),
448 }]
449 }
450
451 /// The marked place, with nothing to do first: the room is made by
452 /// [`Go::read_call`], which routes the value through `zdwire.Ptr`.
453 fn optional_destination(base: &str, field: &str) -> (Vec<Self::Stmt>, Self::Expr) {
454 (
455 Vec::new(),
456 GoExpr::OptionalSlot(inner(Self::member(base, field))),
457 )
458 }
459
460 /// The operators as given: Go spells `&&`, `||` and the comparisons
461 /// the way the walk does. `=` never reaches a rendered expression —
462 /// [`Go::perform`] turns it into an assignment statement.
463 fn binary(op: &'static str, lhs: Self::Expr, rhs: Self::Expr) -> Self::Expr {
464 plain(go::Expr::Binary {
465 op,
466 lhs: Box::new(inner(lhs)),
467 rhs: Box::new(inner(rhs)),
468 })
469 }
470
471 /// `!a`, and `^a` where the walk wrote C's `~`: Go's bitwise
472 /// complement is the unary `^`.
473 fn unary(op: &'static str, value: Self::Expr) -> Self::Expr {
474 plain(go::Expr::Unary {
475 op: match op {
476 "~" => "^",
477 other => other,
478 },
479 value: Box::new(inner(value)),
480 })
481 }
482
483 fn paren(value: Self::Expr) -> Self::Expr {
484 plain(go::Expr::Paren(Box::new(inner(value))))
485 }
486
487 /// `!a && b`: Go defines `<` on ordered types only, and `bool` is not
488 /// one ("operator < not defined on bool", measured in the matrix
489 /// refresh of 2026-10-01 on `map-key-boolean` under the Fast DDS and
490 /// OpenDDS dialects, which order map entries by key). `false` before
491 /// `true` is [`zerodds_ast_layout::MapKeyOrdering::UnsignedInteger`]'s
492 /// order for a `boolean`.
493 fn bool_less(left: Self::Expr, right: Self::Expr) -> Self::Expr {
494 Self::binary("&&", Self::unary("!", Self::paren(left)), right)
495 }
496
497 /// `T(v)` — a Go conversion, with two rewrites.
498 ///
499 /// A `bool` discriminator's labels arrive as integers and
500 /// `bool(1)` is not a Go conversion; the label becomes the literal.
501 /// And the walk's `signed char` — the C spelling of a map key's
502 /// signed order — is `int8` here, which reads a `uint8` key as the
503 /// two's complement the corpus was recorded under.
504 fn cast(type_: Self::Type, value: Self::Expr) -> Self::Expr {
505 if type_ == go::Type::named(SIGNED_CHAR) {
506 return plain(go::Expr::Convert {
507 type_: Box::new(go::Type::Primitive(go::Primitive::Int8)),
508 value: Box::new(inner(value)),
509 });
510 }
511 if type_ == go::Type::Primitive(go::Primitive::Bool) {
512 if let GoExpr::Plain(go::Expr::Literal(Literal::Int(text))) = &value {
513 return plain(go::Expr::Literal(Literal::Bool(text != "0")));
514 }
515 }
516 plain(go::Expr::Convert {
517 type_: Box::new(type_),
518 value: Box::new(inner(value)),
519 })
520 }
521
522 /// The value, unchanged: Go hands a slice by header, an array by
523 /// value and a struct by value, and the codec's parameter says
524 /// which.
525 fn handoff(value: Self::Expr, _form: Handoff) -> Self::Expr {
526 value
527 }
528
529 /// A call to a package-level function — a generated codec or
530 /// defaulter. `strcmp` is the one name the walk hands here that is
531 /// not one; it is the runtime's `Strcmp`, which spells C11
532 /// §7.24.4's unsigned-octet order in the language whose own string
533 /// order is exactly that.
534 fn call(callee: &str, args: Vec<Self::Expr>) -> Self::Expr {
535 let arguments: Vec<go::Expr> = args.into_iter().map(inner).collect();
536 if callee == "strcmp" {
537 return plain(go::Expr::call(Self::runtime_name("Strcmp"), arguments));
538 }
539 plain(go::Expr::call(go::Expr::name(callee), arguments))
540 }
541
542 /// `w.WriteU32(x)` — a method on the writer or the reader.
543 ///
544 /// One operation is renamed rather than converted: the walk's
545 /// `read_pid()` reads a header **and** its value block is opened by
546 /// the later `finish_pid_read`, which is `ReadPidAs` in this
547 /// runtime — §7.4.1.2 makes the block a wire question, so the call
548 /// may not be left as it stands. The Python column does the same.
549 fn runtime(receiver: &str, method: &str, args: Vec<Self::Expr>) -> Self::Expr {
550 if method == "read_pid" && args.is_empty() {
551 return Self::runtime(
552 receiver,
553 "read_pid_as",
554 vec![Self::constant(Constant::ParameterMember)],
555 );
556 }
557 plain(go::Expr::call(
558 go::Expr::selector(go::Expr::name(receiver), &Self::pascal(method)),
559 args.into_iter().map(inner).collect(),
560 ))
561 }
562
563 /// `runtime/go` carries the three HDDS axes: `BeginPackedElement`
564 /// and `FinishPackedElement` on the writer and
565 /// `ReaderBeginPackedElement` / `ReaderFinishPackedElement` on the
566 /// reader, the DHEADER pair for a sequence element's own length, and
567 /// `WriteWstringUtf8Octets` / `WriteWstringUtf8OctetsBounded` /
568 /// `ReadWstringUtf8Octets`.
569 fn carries_hdds_axes() -> bool {
570 true
571 }
572
573 /// A free function of the runtime — `zdwire.NoDheader()`,
574 /// `zdwire.Utf8Octets(v)` — except `pid_is_user_member`, which is a
575 /// method on the header the reader handed back, the same difference
576 /// as C's free function against Java's static.
577 fn runtime_static(method: &str, args: Vec<Self::Expr>) -> Self::Expr {
578 if method == "pid_is_user_member" {
579 let mut arguments = args.into_iter().map(inner);
580 if let Some(header) = arguments.next() {
581 return plain(go::Expr::call(
582 go::Expr::selector(header, "IsUserMember"),
583 Vec::new(),
584 ));
585 }
586 // The walk always hands the header; a call without one is a
587 // walk that changed under this file, and a method on a name
588 // no scope declares is the loudest thing Go can say here.
589 return plain(go::Expr::call(
590 Self::runtime_name("IsUserMember"),
591 Vec::new(),
592 ));
593 }
594 plain(go::Expr::call(
595 Self::runtime_name(&Self::pascal(method)),
596 args.into_iter().map(inner).collect(),
597 ))
598 }
599
600 // -- statements ----------------------------------------------------
601
602 /// `var name T = value`. The type is written because the walk knows
603 /// it and a reader of the generated file then sees the width — and
604 /// because a `var` with a type is definite whether or not the value
605 /// is there, which the header handle declared before a loop needs.
606 fn declare(type_: Self::Type, name: &str, value: Option<Self::Expr>) -> Self::Stmt {
607 go::Stmt::Var {
608 name: Ident::new(name),
609 type_: Some(type_),
610 value: value.map(inner),
611 }
612 }
613
614 /// An expression evaluated for its effect — or, where the walk
615 /// built an assignment, the assignment: `a = b` is a statement in
616 /// Go and not an expression, and this is the only place the walk's
617 /// `=` arrives.
618 fn perform(value: Self::Expr) -> Self::Stmt {
619 if let GoExpr::Plain(go::Expr::Binary { op: "=", lhs, rhs }) = value {
620 return go::Stmt::Assign {
621 place: *lhs,
622 value: *rhs,
623 };
624 }
625 go::Stmt::Expr(inner(value))
626 }
627
628 /// `return v`: a Go decoder answers with the sample it built, so a
629 /// bare `return` — C's shape, for a decoder that wrote through a
630 /// pointer — would answer nothing at all and not compile.
631 fn give_back(value: Option<Self::Expr>) -> Self::Stmt {
632 go::Stmt::Return(Some(match value {
633 Some(value) => inner(value),
634 None => go::Expr::name("v"),
635 }))
636 }
637
638 fn when(
639 condition: Self::Expr,
640 then: Self::Block,
641 otherwise: Option<Self::Block>,
642 ) -> Self::Stmt {
643 go::Stmt::If {
644 condition: inner(condition),
645 then,
646 otherwise: otherwise.filter(|body| !body.is_empty()),
647 }
648 }
649
650 /// `for i := uint32(0); i < bound; i++`. The counter is a `uint32`
651 /// because every bound the walk hands over is one — a count off the
652 /// wire, `uint32(len(v))`, or an untyped constant that converts.
653 fn count_up(counter: &str, bound: Self::Expr, body: Self::Block) -> Self::Stmt {
654 go::Stmt::For {
655 counter: Ident::new(counter),
656 init: Box::new(go::Expr::Convert {
657 type_: Box::new(go::Type::Primitive(go::Primitive::UInt32)),
658 value: Box::new(go::Expr::int(0)),
659 }),
660 bound: Box::new(inner(bound)),
661 body,
662 }
663 }
664
665 fn until(condition: Self::Expr, body: Self::Block) -> Self::Stmt {
666 go::Stmt::While {
667 condition: inner(condition),
668 body,
669 }
670 }
671
672 fn leave() -> Self::Stmt {
673 go::Stmt::Break
674 }
675
676 fn note(doc: Doc) -> Self::Stmt {
677 go::Stmt::Comment(doc)
678 }
679
680 /// `_ = expr`: Go has no `(void)` cast and a bare call is already a
681 /// statement, but a *value* read for its effect — a discarded
682 /// DHEADER — has to say it meant the discard, and the blank
683 /// identifier is Go's spelling of exactly that.
684 fn drop_value(value: Self::Expr) -> Self::Stmt {
685 go::Stmt::Assign {
686 place: go::Expr::name("_"),
687 value: inner(value),
688 }
689 }
690
691 /// `_ = name`: Go rejects a local that is assigned and never read
692 /// — "declared and not used" is a compile error, not a warning —
693 /// and `zd_sent` under a framing that writes no sentinel is exactly
694 /// that local.
695 fn mark_used(name: &str) -> Vec<Self::Stmt> {
696 vec![go::Stmt::Assign {
697 place: go::Expr::name("_"),
698 value: go::Expr::name(name),
699 }]
700 }
701
702 /// `place = zd_dec_T(r, behaviour)` — and through `zdwire.Ptr`
703 /// where the place is an `@optional` member's pointer field: the
704 /// decoder answers a `T`, the field holds a `*T`, and Go has no
705 /// address-of on a call's result.
706 fn read_call(
707 callee: &str,
708 place: Self::Expr,
709 _form: Handoff,
710 behaviour: Self::Expr,
711 ) -> Self::Stmt {
712 let decoded = go::Expr::call(
713 go::Expr::name(callee),
714 vec![go::Expr::name("r"), inner(behaviour)],
715 );
716 match place {
717 GoExpr::OptionalSlot(field) => go::Stmt::Assign {
718 place: field,
719 value: go::Expr::call(Self::runtime_name("Ptr"), vec![decoded]),
720 },
721 GoExpr::Plain(place) => go::Stmt::Assign {
722 place,
723 value: decoded,
724 },
725 }
726 }
727
728 /// `v = zd_dec_T(r, zd_tc)` — a `typedef`, handed straight on.
729 fn read_forward(callee: &str) -> Self::Stmt {
730 Self::read_call(
731 callee,
732 Self::whole("v"),
733 Handoff::Value,
734 Self::name("zd_tc"),
735 )
736 }
737
738 fn block(statements: Vec<Self::Stmt>) -> Self::Block {
739 statements
740 }
741
742 fn into_statements(block: Self::Block) -> Vec<Self::Stmt> {
743 block
744 }
745
746 /// `v = make([]T, n)`, and the fill after it: a Go slice's slots
747 /// are storage the moment `make` returns, for a sequence's whole
748 /// elements and for a map entry's two halves alike, so
749 /// `assigned_whole` makes no difference here.
750 fn make_room(
751 base: &str,
752 element: Self::Type,
753 count: Self::Expr,
754 _assigned_whole: bool,
755 fill: Vec<Self::Stmt>,
756 ) -> Vec<Self::Stmt> {
757 let mut out = vec![go::Stmt::Assign {
758 place: go::Expr::name(base),
759 value: go::Expr::Make {
760 type_: Box::new(go::Type::slice(element)),
761 length: Box::new(inner(count)),
762 },
763 }];
764 out.extend(fill);
765 out
766 }
767
768 /// `v = v[:count]` — §7.3.1.2.1.12.3's TRIM, applied after every
769 /// element has been read.
770 fn keep_first(base: &str, count: Self::Expr) -> Vec<Self::Stmt> {
771 vec![go::Stmt::Assign {
772 place: go::Expr::name(base),
773 value: go::Expr::SliceTo {
774 value: Box::new(go::Expr::name(base)),
775 high: Box::new(inner(count)),
776 },
777 }]
778 }
779
780 /// The subscript, with nothing around it: a Go slice element is a
781 /// real place at every element type — there is no
782 /// `std::vector<bool>` here.
783 fn sequence_element(
784 base: &str,
785 index: Self::Expr,
786 _element: Self::Type,
787 ) -> (Vec<Self::Stmt>, Self::Expr, Vec<Self::Stmt>) {
788 (
789 Vec::new(),
790 Self::index(Self::elements(base), index),
791 Vec::new(),
792 )
793 }
794
795 // -- assembly ------------------------------------------------------
796
797 /// `func Zd_enc_T(w *zdwire.Writer, v T)` and
798 /// `func Zd_dec_T(r *zdwire.Reader, zd_tc zdwire.TryConstruct) T`.
799 /// The upper-case first letter is Go's visibility; see
800 /// [`exported_name`].
801 ///
802 /// A forward declaration is answered with nothing: Go resolves a
803 /// package-level name wherever it is declared, so a recursive type
804 /// needs no declaration ahead of its body.
805 /// Go's visibility is its spelling; see [`exported_name`].
806 fn published_name(name: String) -> String {
807 exported_name(&name)
808 }
809
810 fn routine(spec: Routine<Self>) -> Option<Self::Decl> {
811 let body = spec.body?;
812 let mut params = vec![go::Param {
813 name: Some(Ident::new(if spec.writing { "w" } else { "r" })),
814 type_: go::Type::pointer(go::Type::named(if spec.writing {
815 "zdwire.Writer"
816 } else {
817 "zdwire.Reader"
818 })),
819 }];
820 if spec.writing {
821 params.push(go::Param {
822 name: Some(Ident::new("v")),
823 type_: spec.sample.clone(),
824 });
825 }
826 for (type_, name) in spec.extra {
827 params.push(go::Param {
828 name: Some(Ident::new(&name)),
829 type_,
830 });
831 }
832 let (results, body) = if spec.writing {
833 (Vec::new(), body)
834 } else {
835 // The sample starts as Table 9's value, through the
836 // generated defaulter, so every position the stream does
837 // not carry is already what the clause says it is — the
838 // same shape the Python column's `reading_body` writes.
839 let defaulter = crate::default_go::defaulter_from_decoder(&spec.name);
840 let mut statements = vec![go::Stmt::Var {
841 name: Ident::new("v"),
842 type_: None,
843 value: Some(go::Expr::call(go::Expr::name(&defaulter), Vec::new())),
844 }];
845 statements.extend(body);
846 statements.push(go::Stmt::Return(Some(go::Expr::name("v"))));
847 (vec![spec.sample], statements)
848 };
849 Some(go::Decl::Func(go::FuncDecl {
850 doc: spec.doc,
851 // Already the published spelling: `codec_name` and
852 // `decoder_name` put it through [`Go::published_name`]
853 // before the walk ever names the routine.
854 name: Ident::new(&spec.name),
855 params,
856 results,
857 body,
858 }))
859 }
860
861 /// A call to the generated defaulter, which is Table 9 once per
862 /// type rather than once per position.
863 ///
864 /// # Errors
865 /// Never; the signature matches the emitters around it.
866 fn default_into(
867 ast: &Ast,
868 id: Id<TypeNode>,
869 place: Self::Expr,
870 ) -> Result<Self::Stmt, EncodeError> {
871 Ok(go::Stmt::Assign {
872 place: inner(place),
873 value: go::Expr::call(
874 go::Expr::name(&crate::default_go::defaulter_name(ast, id)),
875 Vec::new(),
876 ),
877 })
878 }
879
880 /// # Errors
881 /// As [`crate::default_go::member_default`]: a `@default` this
882 /// column cannot spell is refused rather than silently replaced by
883 /// Table 9's zero.
884 fn member_default(
885 ast: &Ast,
886 node: &MemberNode,
887 type_: Id<TypeNode>,
888 place: Self::Expr,
889 ) -> Result<Vec<Self::Stmt>, EncodeError> {
890 crate::default_go::member_default(ast, node, type_, inner(place))
891 }
892}
893
894/// The bound a `string`, a `sequence` or a `map` carries, as the suffix
895/// that keeps two of them apart.
896///
897/// The tag names the routine a hoisted collection's codec is emitted
898/// under, and one body is emitted per name. With the bound left out,
899/// `sequence<long>` and `sequence<long, 4>` are one name and whichever
900/// was declared first decides: measured on
901/// `@final struct S { sequence<long> b; sequence<long, 4> a; };`, the
902/// generated Go held no `zd_over` check at all, so `a` accepted a
903/// stream of any length; with the members the other way round both got
904/// the bounded body, so `b` refused — and truncated — a length the IDL
905/// puts no limit on. `string<4>` beside `string` and `wstring<4>`
906/// beside `wstring` collapse the same way, onto the trimming call.
907/// Either way the reader answered with a value the IDL does not
908/// describe and said nothing, which is why the bound is part of the
909/// name. The same suffix the Zig column carries.
910///
911/// An `array` needs nothing here: its `dims` are already in the tag,
912/// under the `Array` arm below.
913fn bound_tag(ast: &Ast, bound: &zerodds_ast::Bound) -> String {
914 match bound.expr() {
915 None => String::new(),
916 // `_b` and not a bare `_n`: `seq_long_4` is what the `Array`
917 // arm answers for an array of four `long`, and the two are
918 // different wire forms.
919 Some(expr) => match ast.eval_integral(expr) {
920 Ok(limit) => format!("_b{limit}"),
921 // As the array's `_x`: a bound that does not evaluate is
922 // still not the unbounded form, and the materialiser
923 // reports the expression that would not evaluate.
924 Err(_) => "_bx".to_string(),
925 },
926 }
927}
928
929/// The name a codec function takes for the type it codes.
930///
931/// The IDL kind and not the Go spelling, for the reason the Python
932/// column's `type_tag` gives at length: `octet` and `uint8` are one Go
933/// type and one wire form here, but `double` and `long double` are
934/// `float64` and `string`, and a tag built from the Go type would give
935/// a `fixed<5,2>` and a `wstring` one codec. A declared type takes its
936/// flattened Go name, which two IDL declarations cannot share —
937/// `adapter/go.rs`'s `remember` refuses the collision before a codec is
938/// generated.
939///
940/// zerodds-lint: recursion-depth 64 (collection nesting; bounded by `MAX_TYPE_DEPTH`)
941#[must_use]
942pub fn type_tag(ast: &Ast, id: Id<TypeNode>, depth: usize) -> String {
943 if depth > MAX_TYPE_DEPTH {
944 return "unresolved".to_string();
945 }
946 let node = match ast.type_(id) {
947 Ok(node) => node,
948 Err(_) => return "unresolved".to_string(),
949 };
950 match &node.kind {
951 TypeKind::Primitive(kind) => kind.as_str().replace(' ', "_"),
952 TypeKind::String {
953 width: CharWidth::Char,
954 bound,
955 } => format!("string{}", bound_tag(ast, bound)),
956 TypeKind::String {
957 width: CharWidth::WChar,
958 bound,
959 } => format!("wstring{}", bound_tag(ast, bound)),
960 TypeKind::Fixed { digits, scale } => format!("fixed_{digits}_{scale}"),
961 TypeKind::Sequence { element, bound } => match element.resolved() {
962 Some(element) => format!(
963 "seq{}_{}",
964 bound_tag(ast, bound),
965 type_tag(ast, element, depth + 1)
966 ),
967 None => format!("seq_unresolved{}", bound_tag(ast, bound)),
968 },
969 TypeKind::Array { element, dims } => {
970 let mut name = match element.resolved() {
971 Some(element) => format!("arr_{}", type_tag(ast, element, depth + 1)),
972 None => "arr_unresolved".to_string(),
973 };
974 for dim in dims {
975 match ast.eval_integral(*dim) {
976 Ok(length) => name.push_str(&format!("_{length}")),
977 // No number rather than a zero: the name has to
978 // differ from a `[0]` array's, and the materialiser
979 // reports the dimension that would not evaluate by
980 // name.
981 Err(_) => name.push_str("_x"),
982 }
983 }
984 name
985 }
986 TypeKind::Map { key, value, bound } => {
987 let half = |half: Option<Id<TypeNode>>| match half {
988 Some(target) => type_tag(ast, target, depth + 1),
989 None => "unresolved".to_string(),
990 };
991 format!(
992 "map{}_{}_{}",
993 bound_tag(ast, bound),
994 half(key.resolved()),
995 half(value.resolved())
996 )
997 }
998 // An alias is followed through: two IDL names for one shape are
999 // one wire form, and two codecs for it would be two chances to
1000 // differ.
1001 TypeKind::Alias { aliased } => match aliased.resolved() {
1002 Some(target) => type_tag(ast, target, depth + 1),
1003 None => "unresolved".to_string(),
1004 },
1005 TypeKind::ValueBox { boxed } => match boxed.resolved() {
1006 Some(target) => format!("box_{}", type_tag(ast, target, depth + 1)),
1007 None => "box_unresolved".to_string(),
1008 },
1009 TypeKind::Structure { .. }
1010 | TypeKind::Union { .. }
1011 | TypeKind::Enumeration { .. }
1012 | TypeKind::Bitmask { .. }
1013 | TypeKind::Bitset { .. }
1014 | TypeKind::Valuetype { .. }
1015 | TypeKind::Native => go_type_name(ast, id),
1016 TypeKind::Any => "any".to_string(),
1017 TypeKind::ValueBase => "valuebase".to_string(),
1018 TypeKind::ObjectRef { .. } => "objectref".to_string(),
1019 TypeKind::AnnotationType { .. } => "annotation".to_string(),
1020 }
1021}
1022
1023/// A generated name, in the form Go makes visible outside its package.
1024///
1025/// Go has no visibility keyword: an identifier is exported if and only
1026/// if its first character is an upper-case letter (Go specification,
1027/// "Exported identifiers"). Every generated codec, defaulter and key
1028/// holder was named `zd_enc_…`, `zd_def_…`, `zd_kenc_…` — all
1029/// lower-case — so a Go program that imported the generated package
1030/// could not call a single one of them. The probe harness never found
1031/// out: it writes its `main` into the same package.
1032///
1033/// **The first character only.** `ZdEncMax` would be the idiomatic
1034/// spelling and it is not injective — dropping the underscores maps two
1035/// different generated names onto one, and two codecs under one name is
1036/// a collision no compiler would report as one, because the second
1037/// declaration would simply be a redeclaration error somewhere else or,
1038/// worse, not an error at all. Raising the first letter is a bijection
1039/// on this name set: every generated name begins `zd`, and no two
1040/// differ only in that letter.
1041///
1042/// Names that are not generated pass through: the runtime's are reached
1043/// through `Go::runtime_name` and never arrive here, and `strcmp` is
1044/// intercepted by [`Go::call`] before this is asked.
1045pub fn exported_name(name: &str) -> String {
1046 // Idempotent: a name already raised passes through, so a second
1047 // application — `defaulter_name` calling this on a name built from
1048 // an already-published decoder — cannot raise anything twice or
1049 // produce a second spelling of one function.
1050 if !name.starts_with("zd_") {
1051 return name.to_string();
1052 }
1053 let mut chars = name.chars();
1054 match chars.next() {
1055 None => String::new(),
1056 Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
1057 }
1058}